@jamiexiongr/panda-hub 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-C6HLB7NI.mjs → chunk-MXQGMO73.mjs} +2 -2
- package/dist/{chunk-KW522MKT.mjs → chunk-VOWZ4V3B.mjs} +2588 -535
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-OBU4SE67.mjs → src-NI7ZHUVP.mjs} +28 -14
- package/dist/web/assets/{diagnostics-page-CoIdkIQI.js → diagnostics-page-BX9ilH-j.js} +1 -1
- package/dist/web/assets/index-CdvROdNu.css +1 -0
- package/dist/web/assets/index-DPapkx9A.js +208 -0
- package/dist/web/assets/{session-diff-preview-CCCZmf4G.js → session-diff-preview-kDHdPa6S.js} +1 -1
- package/dist/web/assets/{session-file-markdown-preview-Dv-eVJZC.js → session-file-markdown-preview-DKYviOHs.js} +1 -1
- package/dist/web/assets/session-file-preview-page-B5cY0GxO.js +38 -0
- package/dist/web/assets/{web-DeqRViq6.js → web-BCkw8FnT.js} +1 -1
- package/dist/web/assets/{web-D1pcnorX.js → web-CI5VgZbX.js} +1 -1
- package/dist/web/assets/{web-vTFNt5EQ.js → web-CcQ2B2v6.js} +1 -1
- package/dist/web/assets/{web-Dy_S-Kpy.js → web-Cg2SPsir.js} +1 -1
- package/dist/web/assets/{web-CwfmVur7.js → web-CiNw0NeJ.js} +1 -1
- package/dist/web/assets/{xml-CQYUv0yT.js → xml-hyN8lXhw.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-DBWUmekP.css +0 -1
- package/dist/web/assets/index-OTfJjsxL.js +0 -148
- package/dist/web/assets/session-file-preview-page-DK4GwzJS.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(),
|
|
@@ -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),
|
|
@@ -8971,7 +10294,7 @@ const killByPort = async (port) => {
|
|
|
8971
10294
|
for (const pid of pids) {
|
|
8972
10295
|
if (process.platform === 'win32') {
|
|
8973
10296
|
await new Promise((resolve) => {
|
|
8974
|
-
const child = spawn('taskkill', ['/pid', String(pid), '/
|
|
10297
|
+
const child = spawn('taskkill', ['/pid', String(pid), '/f'], {
|
|
8975
10298
|
stdio: 'ignore',
|
|
8976
10299
|
windowsHide: true,
|
|
8977
10300
|
})
|
|
@@ -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-NI7ZHUVP.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,
|
|
@@ -12756,6 +14111,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12756
14111
|
id: session.id,
|
|
12757
14112
|
agent_id: session.agent_id,
|
|
12758
14113
|
project_id: session.project_id,
|
|
14114
|
+
provider: session.provider,
|
|
12759
14115
|
archived: session.archived,
|
|
12760
14116
|
title: session.title,
|
|
12761
14117
|
last_event_at: session.last_event_at,
|
|
@@ -12785,6 +14141,20 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12785
14141
|
context_usage: session.context_usage,
|
|
12786
14142
|
capability: session.capability
|
|
12787
14143
|
});
|
|
14144
|
+
const resolveImageModelVendor = (channel, modelId) => channel?.models.find((model) => model.id === modelId)?.vendor ?? (modelId.toLowerCase().startsWith("grok-") ? "xai" : "openai");
|
|
14145
|
+
const readImageSessionDetail = async (sessionId) => {
|
|
14146
|
+
const record = findImageSessionRecord(sessionId);
|
|
14147
|
+
if (!record) {
|
|
14148
|
+
return null;
|
|
14149
|
+
}
|
|
14150
|
+
return {
|
|
14151
|
+
generated_at: isoNow6(),
|
|
14152
|
+
session: toWorkspaceSessionDetail(record.session),
|
|
14153
|
+
turns: record.turns.map(
|
|
14154
|
+
(turn) => imageSessionStore.toProtocolTurn(record, turn, buildImageAssetUrl)
|
|
14155
|
+
)
|
|
14156
|
+
};
|
|
14157
|
+
};
|
|
12788
14158
|
const buildHubDirectorySnapshot = () => ({
|
|
12789
14159
|
generated_at: snapshot.generated_at,
|
|
12790
14160
|
agents: snapshot.agents
|
|
@@ -12811,7 +14181,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12811
14181
|
const projectIds = new Set(projects.map((project) => project.id));
|
|
12812
14182
|
const agentSessions = sortSessionsByActivity2(
|
|
12813
14183
|
snapshot.sessions.filter(
|
|
12814
|
-
(session) => session.agent_id === localAgentId && projectIds.has(session.project_id)
|
|
14184
|
+
(session) => session.agent_id === localAgentId && (isProjectlessSession(session) || projectIds.has(session.project_id))
|
|
12815
14185
|
)
|
|
12816
14186
|
);
|
|
12817
14187
|
const sessions = agentSessions.filter(
|
|
@@ -12822,7 +14192,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12822
14192
|
) ?? null : null;
|
|
12823
14193
|
const displayedSessions = selectedHiddenSession ? sortSessionsByActivity2([selectedHiddenSession, ...sessions]) : sessions;
|
|
12824
14194
|
return {
|
|
12825
|
-
generated_at:
|
|
14195
|
+
generated_at: isoNow6(),
|
|
12826
14196
|
agent: (() => {
|
|
12827
14197
|
const localAgent = snapshot.agents.find((agent) => agent.id === localAgentId) ?? null;
|
|
12828
14198
|
return localAgent ? toWorkspaceAgentSummary(localAgent) : null;
|
|
@@ -12838,12 +14208,17 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12838
14208
|
const offset = parseWorkspaceSessionCursor(input.cursor);
|
|
12839
14209
|
const selectedSessionId = input.selectedSessionId?.trim() ?? "";
|
|
12840
14210
|
const projectId = input.projectId?.trim() ?? "";
|
|
14211
|
+
const projectless = input.projectless === true;
|
|
12841
14212
|
const filteredSessions = sortSessionsByActivity2(
|
|
12842
14213
|
snapshot.sessions.filter((session) => {
|
|
12843
14214
|
if (session.agent_id !== localAgentId) {
|
|
12844
14215
|
return false;
|
|
12845
14216
|
}
|
|
12846
|
-
if (
|
|
14217
|
+
if (projectless) {
|
|
14218
|
+
if (!isProjectlessSession(session)) {
|
|
14219
|
+
return false;
|
|
14220
|
+
}
|
|
14221
|
+
} else if (projectId && session.project_id !== projectId) {
|
|
12847
14222
|
return false;
|
|
12848
14223
|
}
|
|
12849
14224
|
if (input.bucket === "archived") {
|
|
@@ -12856,19 +14231,26 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12856
14231
|
const nextOffset = offset + pageSessions.length;
|
|
12857
14232
|
return {
|
|
12858
14233
|
bucket: input.bucket,
|
|
12859
|
-
project_id: projectId || null,
|
|
14234
|
+
project_id: projectless ? PROJECTLESS_SESSION_PROJECT_ID : projectId || null,
|
|
12860
14235
|
sessions: pageSessions.map(toWorkspaceSessionListItem),
|
|
12861
14236
|
next_cursor: nextOffset < filteredSessions.length ? String(nextOffset) : null,
|
|
12862
14237
|
total_count: filteredSessions.length
|
|
12863
14238
|
};
|
|
12864
14239
|
};
|
|
12865
14240
|
const readWorkspaceSessionDetail = async (sessionId) => {
|
|
14241
|
+
const imageDetail = await readImageSessionDetail(sessionId);
|
|
14242
|
+
if (imageDetail) {
|
|
14243
|
+
return {
|
|
14244
|
+
generated_at: imageDetail.generated_at,
|
|
14245
|
+
session: imageDetail.session
|
|
14246
|
+
};
|
|
14247
|
+
}
|
|
12866
14248
|
const session = await findSnapshotSession(sessionId);
|
|
12867
14249
|
if (!session) {
|
|
12868
14250
|
return null;
|
|
12869
14251
|
}
|
|
12870
14252
|
return {
|
|
12871
|
-
generated_at:
|
|
14253
|
+
generated_at: isoNow6(),
|
|
12872
14254
|
session: toWorkspaceSessionDetail(session)
|
|
12873
14255
|
};
|
|
12874
14256
|
};
|
|
@@ -12910,7 +14292,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12910
14292
|
}
|
|
12911
14293
|
snapshot = {
|
|
12912
14294
|
...snapshot,
|
|
12913
|
-
generated_at:
|
|
14295
|
+
generated_at: isoNow6(),
|
|
12914
14296
|
agents: snapshot.agents.map((agent) => ({
|
|
12915
14297
|
...agent,
|
|
12916
14298
|
project_count: nextProjectCounts.get(agent.id) ?? 0,
|
|
@@ -12923,7 +14305,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12923
14305
|
}
|
|
12924
14306
|
snapshot = {
|
|
12925
14307
|
...snapshot,
|
|
12926
|
-
generated_at:
|
|
14308
|
+
generated_at: isoNow6(),
|
|
12927
14309
|
agents: discoveredAgent ? [
|
|
12928
14310
|
{
|
|
12929
14311
|
...discoveredAgent,
|
|
@@ -12992,7 +14374,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
12992
14374
|
}
|
|
12993
14375
|
};
|
|
12994
14376
|
const createCommandPanel = (input) => {
|
|
12995
|
-
const submittedAt = input.submittedAt ??
|
|
14377
|
+
const submittedAt = input.submittedAt ?? isoNow6();
|
|
12996
14378
|
return {
|
|
12997
14379
|
panel_id: `command-panel-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
12998
14380
|
session_id: input.sessionId,
|
|
@@ -13035,10 +14417,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13035
14417
|
const refreshCodexCommandCatalog = async () => {
|
|
13036
14418
|
if (!codexCommandCatalogRefreshPromise) {
|
|
13037
14419
|
codexCommandCatalogRefreshPromise = (async () => {
|
|
13038
|
-
const cliVersion = await
|
|
14420
|
+
const cliVersion = await detectCodexCliVersion2();
|
|
13039
14421
|
const catalog = codexCommandCatalogSchema.parse({
|
|
13040
14422
|
cli_version: cliVersion,
|
|
13041
|
-
loaded_at:
|
|
14423
|
+
loaded_at: isoNow6(),
|
|
13042
14424
|
cache_ttl_ms: 0,
|
|
13043
14425
|
commands: await loadCodexCommandCatalog(cliVersion)
|
|
13044
14426
|
});
|
|
@@ -13065,6 +14447,21 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13065
14447
|
};
|
|
13066
14448
|
};
|
|
13067
14449
|
const renameSession = async (sessionId, nextName) => {
|
|
14450
|
+
const imageSession = findImageSessionRecord(sessionId);
|
|
14451
|
+
if (imageSession) {
|
|
14452
|
+
await imageSessionStore.mutateSessionRef(sessionId, (session) => ({
|
|
14453
|
+
...session,
|
|
14454
|
+
title: nextName
|
|
14455
|
+
}));
|
|
14456
|
+
syncImageSessionSnapshot(sessionId);
|
|
14457
|
+
broadcastEvent("session.updated", {
|
|
14458
|
+
sessionId,
|
|
14459
|
+
sessionPatch: {
|
|
14460
|
+
title: nextName
|
|
14461
|
+
}
|
|
14462
|
+
});
|
|
14463
|
+
return;
|
|
14464
|
+
}
|
|
13068
14465
|
if (managedSessions.has(sessionId)) {
|
|
13069
14466
|
const managedSession = managedSessions.get(sessionId);
|
|
13070
14467
|
managedSessions.set(sessionId, {
|
|
@@ -13096,28 +14493,276 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13096
14493
|
});
|
|
13097
14494
|
try {
|
|
13098
14495
|
const generated = await liveSessionStream.runOneShotPrompt({
|
|
13099
|
-
cwd: input.
|
|
14496
|
+
cwd: input.cwd,
|
|
13100
14497
|
prompt,
|
|
13101
14498
|
model: input.model,
|
|
13102
|
-
timeoutMs: SESSION_TITLE_GENERATION_TIMEOUT_MS
|
|
14499
|
+
timeoutMs: SESSION_TITLE_GENERATION_TIMEOUT_MS
|
|
14500
|
+
});
|
|
14501
|
+
const nextTitle = sanitizeGeneratedSessionTitle(generated);
|
|
14502
|
+
if (!nextTitle || nextTitle === input.fallbackTitle) {
|
|
14503
|
+
return;
|
|
14504
|
+
}
|
|
14505
|
+
const currentSession = await findSnapshotSession(input.sessionId);
|
|
14506
|
+
if (!currentSession || currentSession.title !== input.fallbackTitle) {
|
|
14507
|
+
return;
|
|
14508
|
+
}
|
|
14509
|
+
await renameSession(input.sessionId, nextTitle);
|
|
14510
|
+
} catch (error) {
|
|
14511
|
+
diagnosticLogger.warn({
|
|
14512
|
+
sessionId: input.sessionId,
|
|
14513
|
+
projectId: input.project?.id ?? PROJECTLESS_SESSION_PROJECT_ID,
|
|
14514
|
+
projectPath: input.cwd,
|
|
14515
|
+
model: input.model,
|
|
14516
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
14517
|
+
}, "Failed to generate background session title.");
|
|
14518
|
+
}
|
|
14519
|
+
};
|
|
14520
|
+
const shouldGenerateImageSessionTitle = (session, turnCount) => turnCount === 0 && IMAGE_SESSION_AUTO_TITLE_PLACEHOLDERS.has(session.title.trim());
|
|
14521
|
+
const submitImageSessionPrompt = async (input) => {
|
|
14522
|
+
const sessionRecord = findImageSessionRecord(input.sessionId);
|
|
14523
|
+
if (!sessionRecord) {
|
|
14524
|
+
throw new Error("Image session not found.");
|
|
14525
|
+
}
|
|
14526
|
+
if (sessionRecord.session.archived) {
|
|
14527
|
+
throw new Error("Archived image sessions cannot accept new input.");
|
|
14528
|
+
}
|
|
14529
|
+
const storedSettings = imageSessionStore.getSettings();
|
|
14530
|
+
const activeChannel = imageSessionStore.getActiveSettingsChannel();
|
|
14531
|
+
const resolvedSettings = buildResolvedImageProviderSettings({
|
|
14532
|
+
activeChannelId: storedSettings.active_channel_id,
|
|
14533
|
+
channels: storedSettings.channels.map((channel) => ({
|
|
14534
|
+
id: channel.id,
|
|
14535
|
+
remark: channel.remark,
|
|
14536
|
+
baseUrl: channel.base_url,
|
|
14537
|
+
storedApiKey: channel.api_key,
|
|
14538
|
+
models: channel.models,
|
|
14539
|
+
updatedAt: channel.updated_at
|
|
14540
|
+
})),
|
|
14541
|
+
updatedAt: storedSettings.updated_at
|
|
14542
|
+
});
|
|
14543
|
+
const apiKey = activeChannel?.api_key?.trim() || resolveImageProviderApiKey();
|
|
14544
|
+
if (!apiKey) {
|
|
14545
|
+
throw new Error("No image API key found. Configure one in settings or environment variables.");
|
|
14546
|
+
}
|
|
14547
|
+
const resolvedVendor = resolveImageModelVendor(activeChannel, input.model);
|
|
14548
|
+
const effectiveModeration = resolvedVendor === "openai" ? input.moderation : "auto";
|
|
14549
|
+
if (resolvedVendor === "xai" && (input.size === "3840x2160" || input.size === "2160x3840")) {
|
|
14550
|
+
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");
|
|
14551
|
+
}
|
|
14552
|
+
const sourceAssets = [];
|
|
14553
|
+
let maskAsset = null;
|
|
14554
|
+
let maskedSourceAssetId = null;
|
|
14555
|
+
for (const source of input.sources) {
|
|
14556
|
+
if (source.kind === "upload") {
|
|
14557
|
+
if (!source.data_url) {
|
|
14558
|
+
throw new Error(`Uploaded image ${source.name} is missing content.`);
|
|
14559
|
+
}
|
|
14560
|
+
const savedAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14561
|
+
sessionId: input.sessionId,
|
|
14562
|
+
kind: "source",
|
|
14563
|
+
name: source.name,
|
|
14564
|
+
dataUrl: source.data_url,
|
|
14565
|
+
mimeType: source.mime_type ?? null
|
|
14566
|
+
});
|
|
14567
|
+
sourceAssets.push(savedAsset);
|
|
14568
|
+
if (!maskAsset && source.mask_data_url) {
|
|
14569
|
+
maskAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14570
|
+
sessionId: input.sessionId,
|
|
14571
|
+
kind: "mask",
|
|
14572
|
+
name: `${source.name}-mask.png`,
|
|
14573
|
+
dataUrl: source.mask_data_url,
|
|
14574
|
+
mimeType: "image/png"
|
|
14575
|
+
});
|
|
14576
|
+
maskedSourceAssetId = savedAsset.id;
|
|
14577
|
+
}
|
|
14578
|
+
continue;
|
|
14579
|
+
}
|
|
14580
|
+
const existing = source.asset_id ? findImageAssetRecordById(source.asset_id)?.asset ?? null : null;
|
|
14581
|
+
if (!existing) {
|
|
14582
|
+
throw new Error(`Referenced image ${source.name} was not found.`);
|
|
14583
|
+
}
|
|
14584
|
+
sourceAssets.push(existing);
|
|
14585
|
+
if (!maskAsset && source.mask_data_url) {
|
|
14586
|
+
maskAsset = await imageSessionStore.saveDataUrlAsset({
|
|
14587
|
+
sessionId: input.sessionId,
|
|
14588
|
+
kind: "mask",
|
|
14589
|
+
name: `${source.name}-mask.png`,
|
|
14590
|
+
dataUrl: source.mask_data_url,
|
|
14591
|
+
mimeType: "image/png"
|
|
14592
|
+
});
|
|
14593
|
+
maskedSourceAssetId = existing.id;
|
|
14594
|
+
}
|
|
14595
|
+
}
|
|
14596
|
+
const orderedSourceAssets = maskedSourceAssetId ? [
|
|
14597
|
+
...sourceAssets.filter((asset) => asset.id === maskedSourceAssetId),
|
|
14598
|
+
...sourceAssets.filter((asset) => asset.id !== maskedSourceAssetId)
|
|
14599
|
+
] : sourceAssets;
|
|
14600
|
+
const operation = orderedSourceAssets.length > 0 ? "edit" : "generate";
|
|
14601
|
+
const shouldGenerateTitle = shouldGenerateImageSessionTitle(
|
|
14602
|
+
sessionRecord.session,
|
|
14603
|
+
sessionRecord.turns.length
|
|
14604
|
+
);
|
|
14605
|
+
const appendTargetTurn = input.appendToTurnId ? sessionRecord.turns.find((turn) => turn.id === input.appendToTurnId) ?? null : null;
|
|
14606
|
+
if (input.appendToTurnId && !appendTargetTurn) {
|
|
14607
|
+
throw new Error("Image turn not found.");
|
|
14608
|
+
}
|
|
14609
|
+
if (appendTargetTurn && appendTargetTurn.status !== "completed") {
|
|
14610
|
+
throw new Error("Only completed image turns can be refreshed.");
|
|
14611
|
+
}
|
|
14612
|
+
const startedRecord = appendTargetTurn ? await imageSessionStore.mutateSessionRef(input.sessionId, (current) => ({
|
|
14613
|
+
...current,
|
|
14614
|
+
latest_assistant_message: appendTargetTurn.operation === "edit" ? "Editing image..." : "Generating image...",
|
|
14615
|
+
last_event_at: isoNow6(),
|
|
14616
|
+
run_state: "running",
|
|
14617
|
+
run_state_changed_at: isoNow6(),
|
|
14618
|
+
capability: {
|
|
14619
|
+
...current.capability,
|
|
14620
|
+
can_send_input: false
|
|
14621
|
+
}
|
|
14622
|
+
})) : await imageSessionStore.startTurn({
|
|
14623
|
+
sessionId: input.sessionId,
|
|
14624
|
+
prompt: input.prompt,
|
|
14625
|
+
operation,
|
|
14626
|
+
model: input.model,
|
|
14627
|
+
quality: input.quality,
|
|
14628
|
+
size: input.size,
|
|
14629
|
+
inputFidelity: input.inputFidelity,
|
|
14630
|
+
aspectRatio: input.aspectRatio,
|
|
14631
|
+
outputFormat: input.outputFormat,
|
|
14632
|
+
outputCompression: input.outputCompression,
|
|
14633
|
+
background: input.background,
|
|
14634
|
+
moderation: effectiveModeration,
|
|
14635
|
+
n: input.n,
|
|
14636
|
+
sourceAssetIds: orderedSourceAssets.map((asset) => asset.id)
|
|
14637
|
+
});
|
|
14638
|
+
const startedTurn = appendTargetTurn ?? startedRecord.turns[startedRecord.turns.length - 1];
|
|
14639
|
+
if (!startedTurn) {
|
|
14640
|
+
throw new Error("Unable to create image session turn.");
|
|
14641
|
+
}
|
|
14642
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14643
|
+
broadcastEvent("session.updated", {
|
|
14644
|
+
sessionId: input.sessionId,
|
|
14645
|
+
sessionPatch: {
|
|
14646
|
+
run_state: "running",
|
|
14647
|
+
run_state_changed_at: startedRecord.session.run_state_changed_at,
|
|
14648
|
+
last_event_at: startedRecord.session.last_event_at,
|
|
14649
|
+
summary: startedRecord.session.summary,
|
|
14650
|
+
latest_assistant_message: startedRecord.session.latest_assistant_message
|
|
14651
|
+
}
|
|
14652
|
+
});
|
|
14653
|
+
try {
|
|
14654
|
+
const providerInputs = await Promise.all(
|
|
14655
|
+
orderedSourceAssets.map(async (asset) => ({
|
|
14656
|
+
name: asset.name,
|
|
14657
|
+
mimeType: asset.mime_type ?? toMimeTypeFromFilename(asset.name),
|
|
14658
|
+
buffer: await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(asset))
|
|
14659
|
+
}))
|
|
14660
|
+
);
|
|
14661
|
+
const providerMask = maskAsset ? {
|
|
14662
|
+
name: maskAsset.name,
|
|
14663
|
+
mimeType: maskAsset.mime_type ?? "image/png",
|
|
14664
|
+
buffer: await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(maskAsset))
|
|
14665
|
+
} : null;
|
|
14666
|
+
const providerResult = await submitImageRequest({
|
|
14667
|
+
baseUrl: resolveImageProviderBaseUrl(activeChannel?.base_url ?? resolvedSettings.base_url),
|
|
14668
|
+
apiKey,
|
|
14669
|
+
prompt: input.prompt,
|
|
14670
|
+
model: input.model,
|
|
14671
|
+
vendor: resolvedVendor,
|
|
14672
|
+
quality: input.quality,
|
|
14673
|
+
size: input.size,
|
|
14674
|
+
inputFidelity: input.inputFidelity,
|
|
14675
|
+
aspectRatio: input.aspectRatio,
|
|
14676
|
+
outputFormat: input.outputFormat,
|
|
14677
|
+
outputCompression: input.outputCompression,
|
|
14678
|
+
background: input.background,
|
|
14679
|
+
moderation: effectiveModeration,
|
|
14680
|
+
n: input.n,
|
|
14681
|
+
inputs: providerInputs,
|
|
14682
|
+
mask: providerMask
|
|
13103
14683
|
});
|
|
13104
|
-
const
|
|
13105
|
-
|
|
13106
|
-
|
|
14684
|
+
const outputAssetIds = [];
|
|
14685
|
+
for (const output of providerResult.outputs) {
|
|
14686
|
+
const savedOutput = await imageSessionStore.saveBufferAsset({
|
|
14687
|
+
sessionId: input.sessionId,
|
|
14688
|
+
turnId: startedTurn.id,
|
|
14689
|
+
kind: "generated",
|
|
14690
|
+
name: output.name,
|
|
14691
|
+
buffer: output.buffer,
|
|
14692
|
+
mimeType: output.mimeType
|
|
14693
|
+
});
|
|
14694
|
+
outputAssetIds.push(savedOutput.id);
|
|
13107
14695
|
}
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
14696
|
+
await imageSessionStore.finishTurn({
|
|
14697
|
+
sessionId: input.sessionId,
|
|
14698
|
+
turnId: startedTurn.id,
|
|
14699
|
+
status: "completed",
|
|
14700
|
+
outputAssetIds,
|
|
14701
|
+
appendOutputs: Boolean(appendTargetTurn)
|
|
14702
|
+
});
|
|
14703
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14704
|
+
const detail = await readImageSessionDetail(input.sessionId);
|
|
14705
|
+
if (!detail) {
|
|
14706
|
+
throw new Error("Unable to load image session detail.");
|
|
13111
14707
|
}
|
|
13112
|
-
|
|
13113
|
-
} catch (error) {
|
|
13114
|
-
diagnosticLogger.warn({
|
|
14708
|
+
broadcastEvent("session.updated", {
|
|
13115
14709
|
sessionId: input.sessionId,
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
14710
|
+
sessionPatch: {
|
|
14711
|
+
run_state: detail.session.run_state,
|
|
14712
|
+
run_state_changed_at: detail.session.run_state_changed_at,
|
|
14713
|
+
last_event_at: detail.session.last_event_at,
|
|
14714
|
+
latest_assistant_message: detail.session.latest_assistant_message
|
|
14715
|
+
}
|
|
14716
|
+
});
|
|
14717
|
+
if (shouldGenerateTitle) {
|
|
14718
|
+
const baseDirectory = await getPandaLocalBaseDirectory();
|
|
14719
|
+
void generateSessionTitleInBackground({
|
|
14720
|
+
sessionId: input.sessionId,
|
|
14721
|
+
project: null,
|
|
14722
|
+
cwd: baseDirectory,
|
|
14723
|
+
fallbackTitle: sessionRecord.session.title,
|
|
14724
|
+
message: input.prompt,
|
|
14725
|
+
attachments: [],
|
|
14726
|
+
model: DEFAULT_SESSION_TITLE_GENERATION_MODEL
|
|
14727
|
+
});
|
|
14728
|
+
}
|
|
14729
|
+
return detail;
|
|
14730
|
+
} catch (error) {
|
|
14731
|
+
if (appendTargetTurn) {
|
|
14732
|
+
await imageSessionStore.mutateSessionRef(input.sessionId, (current) => ({
|
|
14733
|
+
...current,
|
|
14734
|
+
latest_assistant_message: `Generated ${appendTargetTurn.output_asset_ids.length} image(s)`,
|
|
14735
|
+
last_event_at: isoNow6(),
|
|
14736
|
+
run_state: "completed",
|
|
14737
|
+
run_state_changed_at: isoNow6(),
|
|
14738
|
+
capability: {
|
|
14739
|
+
...current.capability,
|
|
14740
|
+
can_send_input: !current.archived
|
|
14741
|
+
}
|
|
14742
|
+
}));
|
|
14743
|
+
} else {
|
|
14744
|
+
await imageSessionStore.finishTurn({
|
|
14745
|
+
sessionId: input.sessionId,
|
|
14746
|
+
turnId: startedTurn.id,
|
|
14747
|
+
status: "failed",
|
|
14748
|
+
error: error instanceof Error ? error.message : "Image request failed.",
|
|
14749
|
+
outputAssetIds: []
|
|
14750
|
+
});
|
|
14751
|
+
}
|
|
14752
|
+
syncImageSessionSnapshot(input.sessionId);
|
|
14753
|
+
const detail = await readImageSessionDetail(input.sessionId);
|
|
14754
|
+
if (detail) {
|
|
14755
|
+
broadcastEvent("session.updated", {
|
|
14756
|
+
sessionId: input.sessionId,
|
|
14757
|
+
sessionPatch: {
|
|
14758
|
+
run_state: detail.session.run_state,
|
|
14759
|
+
run_state_changed_at: detail.session.run_state_changed_at,
|
|
14760
|
+
last_event_at: detail.session.last_event_at,
|
|
14761
|
+
latest_assistant_message: detail.session.latest_assistant_message
|
|
14762
|
+
}
|
|
14763
|
+
});
|
|
14764
|
+
}
|
|
14765
|
+
throw error;
|
|
13121
14766
|
}
|
|
13122
14767
|
};
|
|
13123
14768
|
const executeSessionCommand = async (session, project, rawInput) => {
|
|
@@ -13127,9 +14772,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13127
14772
|
sessionId: session.id,
|
|
13128
14773
|
commandName: "unknown",
|
|
13129
14774
|
commandText: rawInput,
|
|
13130
|
-
title: "
|
|
14775
|
+
title: "Unknown command",
|
|
13131
14776
|
status: "failed",
|
|
13132
|
-
body: "
|
|
14777
|
+
body: "Only input starting with / is recognized as a command."
|
|
13133
14778
|
});
|
|
13134
14779
|
}
|
|
13135
14780
|
const catalog = await readCodexCommandCatalog();
|
|
@@ -13139,9 +14784,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13139
14784
|
sessionId: session.id,
|
|
13140
14785
|
commandName: parsed.name,
|
|
13141
14786
|
commandText: parsed.raw,
|
|
13142
|
-
title:
|
|
14787
|
+
title: `Unknown command /${parsed.name}`,
|
|
13143
14788
|
status: "failed",
|
|
13144
|
-
body: "Panda
|
|
14789
|
+
body: "Panda does not recognize this Codex command yet."
|
|
13145
14790
|
});
|
|
13146
14791
|
}
|
|
13147
14792
|
if (command.availability === "unsupported") {
|
|
@@ -13149,10 +14794,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13149
14794
|
sessionId: session.id,
|
|
13150
14795
|
commandName: command.name,
|
|
13151
14796
|
commandText: parsed.raw,
|
|
13152
|
-
title: `/${command.name}
|
|
14797
|
+
title: `/${command.name} is not implemented`,
|
|
13153
14798
|
description: command.description,
|
|
13154
14799
|
status: "completed",
|
|
13155
|
-
body: "
|
|
14800
|
+
body: "This command is listed but not implemented in Panda yet."
|
|
13156
14801
|
});
|
|
13157
14802
|
}
|
|
13158
14803
|
if (command.name === "status") {
|
|
@@ -13162,17 +14807,17 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13162
14807
|
sessionId: session.id,
|
|
13163
14808
|
commandName: command.name,
|
|
13164
14809
|
commandText: parsed.raw,
|
|
13165
|
-
title: "
|
|
14810
|
+
title: "Current session status",
|
|
13166
14811
|
description: command.description,
|
|
13167
14812
|
status: "completed",
|
|
13168
14813
|
body: [
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
usage ?
|
|
14814
|
+
`Session: ${session.title}`,
|
|
14815
|
+
`Run state: ${session.run_state}`,
|
|
14816
|
+
`Model: ${config.model ?? "unset"}`,
|
|
14817
|
+
`Reasoning effort: ${config.reasoningEffort ?? "default"}`,
|
|
14818
|
+
`Approval policy: ${config.approvalPolicy ?? "default"}`,
|
|
14819
|
+
`Sandbox mode: ${config.sandboxMode ?? "default"}`,
|
|
14820
|
+
usage ? `Context: ${usage.used_tokens}/${usage.total_tokens} (${usage.percent_used.toFixed(1)}%)` : "Context: unavailable"
|
|
13176
14821
|
].join("\n")
|
|
13177
14822
|
});
|
|
13178
14823
|
}
|
|
@@ -13182,10 +14827,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13182
14827
|
sessionId: session.id,
|
|
13183
14828
|
commandName: command.name,
|
|
13184
14829
|
commandText: parsed.raw,
|
|
13185
|
-
title:
|
|
14830
|
+
title: `Skills (${skills.length})`,
|
|
13186
14831
|
description: command.description,
|
|
13187
14832
|
status: "completed",
|
|
13188
|
-
body: skills.length > 0 ? skills.map((skill) => `$${skill.name} ${skill.description}`.trim()).join("\n") : "
|
|
14833
|
+
body: skills.length > 0 ? skills.map((skill) => `$${skill.name} ${skill.description}`.trim()).join("\n") : "No project skills are currently available."
|
|
13189
14834
|
});
|
|
13190
14835
|
}
|
|
13191
14836
|
if (command.name === "mcp") {
|
|
@@ -13194,7 +14839,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13194
14839
|
sessionId: session.id,
|
|
13195
14840
|
commandName: command.name,
|
|
13196
14841
|
commandText: parsed.raw,
|
|
13197
|
-
title: `MCP
|
|
14842
|
+
title: `MCP servers (${servers.length})`,
|
|
13198
14843
|
description: command.description,
|
|
13199
14844
|
status: "completed",
|
|
13200
14845
|
body: servers.length > 0 ? servers.map(
|
|
@@ -13203,8 +14848,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13203
14848
|
`${server.toolCount} tools`,
|
|
13204
14849
|
`${server.resourceCount} resources`,
|
|
13205
14850
|
server.authStatus ? `auth: ${server.authStatus}` : null
|
|
13206
|
-
].filter(Boolean).join("
|
|
13207
|
-
).join("\n") : "
|
|
14851
|
+
].filter(Boolean).join(" ? ")
|
|
14852
|
+
).join("\n") : "No MCP servers are available."
|
|
13208
14853
|
});
|
|
13209
14854
|
}
|
|
13210
14855
|
if (command.name === "compact") {
|
|
@@ -13213,10 +14858,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13213
14858
|
sessionId: session.id,
|
|
13214
14859
|
commandName: command.name,
|
|
13215
14860
|
commandText: parsed.raw,
|
|
13216
|
-
title: "
|
|
14861
|
+
title: "Context compaction requested",
|
|
13217
14862
|
description: command.description,
|
|
13218
14863
|
status: "completed",
|
|
13219
|
-
body: "
|
|
14864
|
+
body: "The compaction request has been sent to Codex."
|
|
13220
14865
|
});
|
|
13221
14866
|
}
|
|
13222
14867
|
if (command.name === "rename") {
|
|
@@ -13226,23 +14871,23 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13226
14871
|
sessionId: session.id,
|
|
13227
14872
|
commandName: command.name,
|
|
13228
14873
|
commandText: parsed.raw,
|
|
13229
|
-
title: "
|
|
14874
|
+
title: "Session renamed",
|
|
13230
14875
|
description: command.description,
|
|
13231
14876
|
status: "completed",
|
|
13232
|
-
body:
|
|
14877
|
+
body: `Current session renamed to ${parsed.args}.`
|
|
13233
14878
|
});
|
|
13234
14879
|
}
|
|
13235
14880
|
const panel = createCommandPanel({
|
|
13236
14881
|
sessionId: session.id,
|
|
13237
14882
|
commandName: command.name,
|
|
13238
14883
|
commandText: parsed.raw,
|
|
13239
|
-
title: "
|
|
14884
|
+
title: "Rename current session",
|
|
13240
14885
|
description: command.description,
|
|
13241
14886
|
status: "awaiting_input",
|
|
13242
|
-
body: "
|
|
14887
|
+
body: "Enter a new session name, then submit it in this panel.",
|
|
13243
14888
|
inputType: "text",
|
|
13244
|
-
inputPlaceholder: "
|
|
13245
|
-
submitLabel: "
|
|
14889
|
+
inputPlaceholder: "Enter a new session name",
|
|
14890
|
+
submitLabel: "Rename"
|
|
13246
14891
|
});
|
|
13247
14892
|
return storeCommandPanel(panel, {
|
|
13248
14893
|
mode: "rename",
|
|
@@ -13255,10 +14900,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13255
14900
|
sessionId: session.id,
|
|
13256
14901
|
commandName: command.name,
|
|
13257
14902
|
commandText: parsed.raw,
|
|
13258
|
-
title: "
|
|
13259
|
-
description: "
|
|
14903
|
+
title: "Select model",
|
|
14904
|
+
description: "Choose the model used for future messages in this Panda session.",
|
|
13260
14905
|
status: "awaiting_input",
|
|
13261
|
-
body: models.length > 0 ? "
|
|
14906
|
+
body: models.length > 0 ? "Select one model from the list to update the session." : "No models are currently available.",
|
|
13262
14907
|
inputType: models.length > 0 ? "choice" : "none",
|
|
13263
14908
|
options: models.map((model) => ({
|
|
13264
14909
|
id: model.id,
|
|
@@ -13275,29 +14920,29 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13275
14920
|
sessionId: session.id,
|
|
13276
14921
|
commandName: command.name,
|
|
13277
14922
|
commandText: parsed.raw,
|
|
13278
|
-
title:
|
|
14923
|
+
title: "/" + command.name + " recognized",
|
|
13279
14924
|
description: command.description,
|
|
13280
14925
|
status: "completed",
|
|
13281
|
-
body: "
|
|
14926
|
+
body: "This command is recognized but not implemented in Panda yet."
|
|
13282
14927
|
});
|
|
13283
14928
|
};
|
|
13284
14929
|
const respondToSessionCommand = async (input) => {
|
|
13285
14930
|
const stored = readStoredCommandPanel(input.sessionId, input.panelId);
|
|
13286
14931
|
if (!stored) {
|
|
13287
|
-
throw new Error("
|
|
14932
|
+
throw new Error("Command panel has expired. Run the command again.");
|
|
13288
14933
|
}
|
|
13289
14934
|
if (stored.mode === "rename") {
|
|
13290
14935
|
const nextName = input.text?.trim() ?? "";
|
|
13291
14936
|
if (!nextName) {
|
|
13292
|
-
throw new Error("
|
|
14937
|
+
throw new Error("Please enter a new session name.");
|
|
13293
14938
|
}
|
|
13294
14939
|
await renameSession(input.sessionId, nextName);
|
|
13295
14940
|
clearStoredCommandPanel(input.panelId);
|
|
13296
14941
|
return {
|
|
13297
14942
|
...stored.panel,
|
|
13298
14943
|
status: "completed",
|
|
13299
|
-
body:
|
|
13300
|
-
updated_at:
|
|
14944
|
+
body: "Current session renamed to " + nextName + ".",
|
|
14945
|
+
updated_at: isoNow6(),
|
|
13301
14946
|
input_type: "none",
|
|
13302
14947
|
input_placeholder: null,
|
|
13303
14948
|
submit_label: null
|
|
@@ -13306,22 +14951,22 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13306
14951
|
if (stored.mode === "model") {
|
|
13307
14952
|
const selectedModelId = input.optionId?.trim() ?? "";
|
|
13308
14953
|
if (!selectedModelId) {
|
|
13309
|
-
throw new Error("
|
|
14954
|
+
throw new Error("Please select a model.");
|
|
13310
14955
|
}
|
|
13311
14956
|
const models = await liveSessionStream.listModels();
|
|
13312
14957
|
const selectedModel = models.find((model) => model.id === selectedModelId);
|
|
13313
14958
|
if (!selectedModel) {
|
|
13314
|
-
throw new Error("
|
|
14959
|
+
throw new Error("Selected model is no longer available. Reopen /model.");
|
|
13315
14960
|
}
|
|
13316
14961
|
clearStoredCommandPanel(input.panelId);
|
|
13317
14962
|
return {
|
|
13318
14963
|
...stored.panel,
|
|
13319
14964
|
status: "completed",
|
|
13320
14965
|
body: [
|
|
13321
|
-
|
|
13322
|
-
selectedModel.defaultReasoningEffort ?
|
|
14966
|
+
"Future messages will prefer " + selectedModel.label + ".",
|
|
14967
|
+
selectedModel.defaultReasoningEffort ? "Default reasoning effort: " + selectedModel.defaultReasoningEffort : null
|
|
13323
14968
|
].filter(Boolean).join("\n"),
|
|
13324
|
-
updated_at:
|
|
14969
|
+
updated_at: isoNow6(),
|
|
13325
14970
|
input_type: "none",
|
|
13326
14971
|
options: [],
|
|
13327
14972
|
effect: {
|
|
@@ -13335,8 +14980,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13335
14980
|
return {
|
|
13336
14981
|
...stored.panel,
|
|
13337
14982
|
status: "failed",
|
|
13338
|
-
body: "
|
|
13339
|
-
updated_at:
|
|
14983
|
+
body: "This command panel does not accept input.",
|
|
14984
|
+
updated_at: isoNow6(),
|
|
13340
14985
|
input_type: "none",
|
|
13341
14986
|
options: []
|
|
13342
14987
|
};
|
|
@@ -13459,7 +15104,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13459
15104
|
lastSnapshotRefreshAt = Date.now();
|
|
13460
15105
|
return snapshot;
|
|
13461
15106
|
}
|
|
13462
|
-
const { discoverLocalCodexData } = await import("./src-
|
|
15107
|
+
const { discoverLocalCodexData } = await import("./src-NI7ZHUVP.mjs");
|
|
13463
15108
|
const discovery = await discoverLocalCodexData({
|
|
13464
15109
|
agentId: localAgentId,
|
|
13465
15110
|
agentName: localAgentName,
|
|
@@ -13472,6 +15117,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13472
15117
|
transport
|
|
13473
15118
|
});
|
|
13474
15119
|
discoveredAgent = discovery.agent;
|
|
15120
|
+
discoveredAgent.provider_availability = ["codex", "image"];
|
|
13475
15121
|
discoveredSessionFiles = discovery.sessionFiles;
|
|
13476
15122
|
const mergedProjects = /* @__PURE__ */ new Map();
|
|
13477
15123
|
for (const project of discovery.projects) {
|
|
@@ -13492,13 +15138,16 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13492
15138
|
for (const session of managedSessions.values()) {
|
|
13493
15139
|
mergedSessions.set(session.id, applyLiveTrackerPatch(session));
|
|
13494
15140
|
}
|
|
15141
|
+
for (const session of getImageSessionRefs()) {
|
|
15142
|
+
mergedSessions.set(session.id, session);
|
|
15143
|
+
}
|
|
13495
15144
|
const threadPrefs = await readPandaThreadPrefs(codexHome);
|
|
13496
15145
|
const orderedProjects = sortByStoredWorkspaceOrder(
|
|
13497
15146
|
[...mergedProjects.values()],
|
|
13498
15147
|
getOrderedWorkspaceRoots(threadPrefs)
|
|
13499
15148
|
);
|
|
13500
15149
|
snapshot = {
|
|
13501
|
-
generated_at:
|
|
15150
|
+
generated_at: isoNow6(),
|
|
13502
15151
|
agents: [],
|
|
13503
15152
|
projects: orderedProjects,
|
|
13504
15153
|
sessions: [...mergedSessions.values()],
|
|
@@ -13535,18 +15184,46 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13535
15184
|
return snapshot.projects.find((project) => project.id === projectId) ?? (await buildSnapshot({ force: true })).projects.find((project) => project.id === projectId) ?? null;
|
|
13536
15185
|
};
|
|
13537
15186
|
const readProjectForSession = async (session) => findSnapshotProject(session.project_id);
|
|
15187
|
+
const getImageSessionRefs = () => imageSessionStore.listSessions().map((record) => record.session);
|
|
15188
|
+
const findImageSessionRecord = (sessionId) => imageSessionStore.getSession(sessionId);
|
|
15189
|
+
const buildImageAssetUrl = (assetId) => {
|
|
15190
|
+
const baseUrl = directBaseUrl?.trim() || discoveredAgent?.direct_base_url?.trim() || "";
|
|
15191
|
+
const pathname = "/api/image-assets/" + encodeURIComponent(assetId);
|
|
15192
|
+
return baseUrl ? baseUrl.replace(/\/+$/, "") + pathname : pathname;
|
|
15193
|
+
};
|
|
15194
|
+
const syncImageSessionSnapshot = (sessionId) => {
|
|
15195
|
+
const record = findImageSessionRecord(sessionId);
|
|
15196
|
+
if (!record) {
|
|
15197
|
+
removeSnapshotSessions([sessionId]);
|
|
15198
|
+
return false;
|
|
15199
|
+
}
|
|
15200
|
+
upsertSnapshotSession(record.session);
|
|
15201
|
+
return true;
|
|
15202
|
+
};
|
|
15203
|
+
const findImageAssetRecordById = (assetId) => {
|
|
15204
|
+
for (const session of imageSessionStore.listSessions()) {
|
|
15205
|
+
const asset = session.assets.find((entry) => entry.id === assetId);
|
|
15206
|
+
if (asset) {
|
|
15207
|
+
return {
|
|
15208
|
+
session,
|
|
15209
|
+
asset
|
|
15210
|
+
};
|
|
15211
|
+
}
|
|
15212
|
+
}
|
|
15213
|
+
return null;
|
|
15214
|
+
};
|
|
13538
15215
|
const readSessionFilePreviewTree = async (session, project, requestedPath) => {
|
|
13539
15216
|
const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
|
|
13540
|
-
const stat = await
|
|
15217
|
+
const stat = await fs9.stat(resolved.realPath).catch((error) => {
|
|
13541
15218
|
if (isMissingPathError(error)) {
|
|
13542
15219
|
throw new Error("File not found.");
|
|
13543
15220
|
}
|
|
13544
15221
|
throw error;
|
|
13545
15222
|
});
|
|
13546
15223
|
if (!stat.isDirectory()) {
|
|
13547
|
-
throw new Error("
|
|
15224
|
+
throw new Error("Target path is not a directory.");
|
|
13548
15225
|
}
|
|
13549
|
-
const entries = await
|
|
15226
|
+
const entries = await fs9.readdir(resolved.realPath, {
|
|
13550
15227
|
withFileTypes: true
|
|
13551
15228
|
});
|
|
13552
15229
|
const nodes = (await Promise.all(
|
|
@@ -13570,21 +15247,21 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13570
15247
|
root_path: SESSION_FILE_PREVIEW_ROOT_PATH,
|
|
13571
15248
|
parent_path: resolved.normalizedPath || null,
|
|
13572
15249
|
nodes,
|
|
13573
|
-
loaded_at:
|
|
15250
|
+
loaded_at: isoNow6()
|
|
13574
15251
|
};
|
|
13575
15252
|
};
|
|
13576
15253
|
const readSessionFilePreviewContent = async (session, project, requestedPath) => {
|
|
13577
15254
|
const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
|
|
13578
|
-
const stat = await
|
|
15255
|
+
const stat = await fs9.stat(resolved.realPath).catch((error) => {
|
|
13579
15256
|
if (isMissingPathError(error)) {
|
|
13580
15257
|
throw new Error("File not found.");
|
|
13581
15258
|
}
|
|
13582
15259
|
throw error;
|
|
13583
15260
|
});
|
|
13584
15261
|
if (!stat.isFile()) {
|
|
13585
|
-
throw new Error("
|
|
15262
|
+
throw new Error("Target path is not a file.");
|
|
13586
15263
|
}
|
|
13587
|
-
const fileName =
|
|
15264
|
+
const fileName = path11.basename(resolved.normalizedPath || resolved.realPath);
|
|
13588
15265
|
const extension = normalizeSessionFilePreviewExtension(fileName);
|
|
13589
15266
|
let fileKind = detectSessionFilePreviewKind(fileName, extension) ?? null;
|
|
13590
15267
|
let previewBuffer = null;
|
|
@@ -13610,10 +15287,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13610
15287
|
is_truncated: true,
|
|
13611
15288
|
content_text: null,
|
|
13612
15289
|
content_base64: null,
|
|
13613
|
-
loaded_at:
|
|
15290
|
+
loaded_at: isoNow6()
|
|
13614
15291
|
};
|
|
13615
15292
|
}
|
|
13616
|
-
const buffer2 = await
|
|
15293
|
+
const buffer2 = await fs9.readFile(resolved.realPath);
|
|
13617
15294
|
return {
|
|
13618
15295
|
session_id: session.id,
|
|
13619
15296
|
project_id: project.id,
|
|
@@ -13627,7 +15304,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13627
15304
|
is_truncated: false,
|
|
13628
15305
|
content_text: null,
|
|
13629
15306
|
content_base64: buffer2.toString("base64"),
|
|
13630
|
-
loaded_at:
|
|
15307
|
+
loaded_at: isoNow6()
|
|
13631
15308
|
};
|
|
13632
15309
|
}
|
|
13633
15310
|
if (fileKind === "binary") {
|
|
@@ -13644,7 +15321,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13644
15321
|
is_truncated: false,
|
|
13645
15322
|
content_text: null,
|
|
13646
15323
|
content_base64: null,
|
|
13647
|
-
loaded_at:
|
|
15324
|
+
loaded_at: isoNow6()
|
|
13648
15325
|
};
|
|
13649
15326
|
}
|
|
13650
15327
|
const buffer = previewBuffer ?? await readPreviewBuffer(resolved.realPath, SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT);
|
|
@@ -13661,7 +15338,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13661
15338
|
is_truncated: stat.size > SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT,
|
|
13662
15339
|
content_text: buffer.toString("utf8"),
|
|
13663
15340
|
content_base64: null,
|
|
13664
|
-
loaded_at:
|
|
15341
|
+
loaded_at: isoNow6()
|
|
13665
15342
|
};
|
|
13666
15343
|
};
|
|
13667
15344
|
const validateHubControlPlaneAuth = (request) => {
|
|
@@ -13675,10 +15352,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13675
15352
|
const ensureGitActionPath = (projectPath, targetPath) => {
|
|
13676
15353
|
const nextPath = normalizeGitWorkspacePath(targetPath?.trim() ?? "");
|
|
13677
15354
|
if (!nextPath) {
|
|
13678
|
-
throw new Error("
|
|
15355
|
+
throw new Error("Missing target file path.");
|
|
13679
15356
|
}
|
|
13680
15357
|
if (!isPathInsideProject(projectPath, nextPath)) {
|
|
13681
|
-
throw new Error("
|
|
15358
|
+
throw new Error("Target file is outside the current workspace.");
|
|
13682
15359
|
}
|
|
13683
15360
|
return nextPath;
|
|
13684
15361
|
};
|
|
@@ -13697,8 +15374,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13697
15374
|
};
|
|
13698
15375
|
const normalizeRollbackPatchText = (input) => {
|
|
13699
15376
|
const normalized = input.replace(/\r\n/g, "\n").trim();
|
|
13700
|
-
return normalized ?
|
|
13701
|
-
` : "";
|
|
15377
|
+
return normalized ? normalized + "\n" : "";
|
|
13702
15378
|
};
|
|
13703
15379
|
const buildRollbackPatchText = (changeSet) => {
|
|
13704
15380
|
if (changeSet.aggregated_diff.trim()) {
|
|
@@ -13716,35 +15392,35 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13716
15392
|
const candidates = [file.path, file.move_path].filter((value) => Boolean(value?.trim()));
|
|
13717
15393
|
for (const candidate of candidates) {
|
|
13718
15394
|
if (!isPathInsideProject(projectPath, candidate)) {
|
|
13719
|
-
throw new Error("
|
|
15395
|
+
throw new Error("This change set contains paths outside the workspace.");
|
|
13720
15396
|
}
|
|
13721
15397
|
}
|
|
13722
15398
|
}
|
|
13723
15399
|
};
|
|
13724
15400
|
const rollbackSessionChangeSet = async (projectPath, changeSet) => {
|
|
13725
15401
|
if (changeSet.status !== "completed") {
|
|
13726
|
-
throw new Error("
|
|
15402
|
+
throw new Error("This change set is not completed yet.");
|
|
13727
15403
|
}
|
|
13728
15404
|
if (changeSet.files.length === 0) {
|
|
13729
|
-
throw new Error("
|
|
15405
|
+
throw new Error("This change set has no file changes to roll back.");
|
|
13730
15406
|
}
|
|
13731
15407
|
ensureChangeSetPathsAreInProject(projectPath, changeSet);
|
|
13732
15408
|
const patchText = buildRollbackPatchText(changeSet);
|
|
13733
15409
|
if (!patchText) {
|
|
13734
|
-
throw new Error("
|
|
15410
|
+
throw new Error("This change set does not contain a reversible patch.");
|
|
13735
15411
|
}
|
|
13736
|
-
const tempDir = await
|
|
13737
|
-
const patchFilePath =
|
|
13738
|
-
await
|
|
15412
|
+
const tempDir = await fs9.mkdtemp(path11.join(os6.tmpdir(), "panda-change-set-rollback-"));
|
|
15413
|
+
const patchFilePath = path11.join(tempDir, "rollback.patch");
|
|
15414
|
+
await fs9.writeFile(patchFilePath, patchText, "utf8");
|
|
13739
15415
|
try {
|
|
13740
15416
|
const patchArgs = ["apply", "--reverse", "--whitespace=nowarn", "--binary", patchFilePath];
|
|
13741
15417
|
await runGitCommand(projectPath, [...patchArgs.slice(0, 1), "--check", ...patchArgs.slice(1)]);
|
|
13742
15418
|
await runGitCommand(projectPath, patchArgs);
|
|
13743
15419
|
} catch (error) {
|
|
13744
|
-
const reason = error instanceof Error && error.message.trim() ? error.message : "Git
|
|
13745
|
-
throw new Error(
|
|
15420
|
+
const reason = error instanceof Error && error.message.trim() ? error.message : "Git could not reverse-apply this patch.";
|
|
15421
|
+
throw new Error("Unable to safely roll back this change: " + reason);
|
|
13746
15422
|
} finally {
|
|
13747
|
-
await
|
|
15423
|
+
await fs9.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
|
|
13748
15424
|
}
|
|
13749
15425
|
};
|
|
13750
15426
|
const discardAllGitWorkspaceChanges = async (projectPath) => {
|
|
@@ -13756,7 +15432,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13756
15432
|
const commitAllGitWorkspaceChanges = async (projectPath, message) => {
|
|
13757
15433
|
const nextMessage = message.trim();
|
|
13758
15434
|
if (!nextMessage) {
|
|
13759
|
-
throw new Error("
|
|
15435
|
+
throw new Error("Please enter a commit message.");
|
|
13760
15436
|
}
|
|
13761
15437
|
await runGitCommand(projectPath, ["add", "-A"]);
|
|
13762
15438
|
await runGitCommand(projectPath, ["commit", "-m", nextMessage]);
|
|
@@ -13764,7 +15440,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13764
15440
|
const switchGitWorkspaceBranch = async (projectPath, branch) => {
|
|
13765
15441
|
const nextBranch = branch.trim();
|
|
13766
15442
|
if (!nextBranch) {
|
|
13767
|
-
throw new Error("
|
|
15443
|
+
throw new Error("Please select a branch.");
|
|
13768
15444
|
}
|
|
13769
15445
|
await runGitCommand(projectPath, ["switch", nextBranch]);
|
|
13770
15446
|
};
|
|
@@ -13796,11 +15472,11 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13796
15472
|
if (process.platform !== "win32") {
|
|
13797
15473
|
return [os6.homedir()];
|
|
13798
15474
|
}
|
|
13799
|
-
const candidates = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) =>
|
|
15475
|
+
const candidates = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) => letter + ":\\");
|
|
13800
15476
|
const checks = await Promise.all(
|
|
13801
15477
|
candidates.map(async (candidate) => {
|
|
13802
15478
|
try {
|
|
13803
|
-
const stat = await
|
|
15479
|
+
const stat = await fs9.stat(candidate);
|
|
13804
15480
|
return stat.isDirectory() ? candidate : null;
|
|
13805
15481
|
} catch {
|
|
13806
15482
|
return null;
|
|
@@ -13863,11 +15539,11 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13863
15539
|
};
|
|
13864
15540
|
const toDirectoryNode = async (targetPath) => {
|
|
13865
15541
|
try {
|
|
13866
|
-
const stat = await
|
|
15542
|
+
const stat = await fs9.stat(targetPath);
|
|
13867
15543
|
if (!stat.isDirectory()) {
|
|
13868
15544
|
return null;
|
|
13869
15545
|
}
|
|
13870
|
-
const entries = await
|
|
15546
|
+
const entries = await fs9.readdir(targetPath, { withFileTypes: true });
|
|
13871
15547
|
const hasChildren = entries.some((entry) => entry.isDirectory());
|
|
13872
15548
|
return {
|
|
13873
15549
|
path: targetPath,
|
|
@@ -13879,7 +15555,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13879
15555
|
}
|
|
13880
15556
|
};
|
|
13881
15557
|
const listDirectories = async (targetPath) => {
|
|
13882
|
-
const roots = !targetPath ? await listDriveRoots() : (await
|
|
15558
|
+
const roots = !targetPath ? await listDriveRoots() : (await fs9.readdir(targetPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path11.join(targetPath, entry.name));
|
|
13883
15559
|
const nodes = await Promise.all(
|
|
13884
15560
|
roots.sort((a, b) => a.localeCompare(b)).map(async (directoryPath) => toDirectoryNode(directoryPath))
|
|
13885
15561
|
);
|
|
@@ -13894,15 +15570,15 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
13894
15570
|
if (!parentPath) {
|
|
13895
15571
|
throw new Error("Parent directory path is required.");
|
|
13896
15572
|
}
|
|
13897
|
-
const resolvedParentPath =
|
|
13898
|
-
const parentStat = await
|
|
15573
|
+
const resolvedParentPath = path11.resolve(parentPath);
|
|
15574
|
+
const parentStat = await fs9.stat(resolvedParentPath).catch(() => null);
|
|
13899
15575
|
if (!parentStat?.isDirectory()) {
|
|
13900
15576
|
throw new Error("Parent directory does not exist.");
|
|
13901
15577
|
}
|
|
13902
15578
|
const directoryName = input.name.trim();
|
|
13903
|
-
const targetPath =
|
|
15579
|
+
const targetPath = path11.join(resolvedParentPath, directoryName);
|
|
13904
15580
|
try {
|
|
13905
|
-
await
|
|
15581
|
+
await fs9.mkdir(targetPath);
|
|
13906
15582
|
} catch (error) {
|
|
13907
15583
|
const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : "";
|
|
13908
15584
|
if (errorCode === "EEXIST") {
|
|
@@ -14002,7 +15678,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14002
15678
|
can_interrupt: true,
|
|
14003
15679
|
can_approve: true,
|
|
14004
15680
|
can_reject: true,
|
|
14005
|
-
|
|
15681
|
+
can_show_git: session.capability.can_show_git,
|
|
15682
|
+
can_show_terminal: session.capability.can_show_terminal
|
|
14006
15683
|
}
|
|
14007
15684
|
});
|
|
14008
15685
|
const unarchiveManagedSession = (sessionId) => {
|
|
@@ -14093,7 +15770,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14093
15770
|
service: serviceName,
|
|
14094
15771
|
mode,
|
|
14095
15772
|
provider: "codex",
|
|
14096
|
-
timestamp:
|
|
15773
|
+
timestamp: isoNow6()
|
|
14097
15774
|
}));
|
|
14098
15775
|
app.get("/api/dev-manager", async (request, reply) => {
|
|
14099
15776
|
try {
|
|
@@ -14148,10 +15825,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14148
15825
|
reply.header("cache-control", "no-store");
|
|
14149
15826
|
reply.header(
|
|
14150
15827
|
"content-disposition",
|
|
14151
|
-
|
|
15828
|
+
'attachment; filename="' + download.artifact.file_name + '"'
|
|
14152
15829
|
);
|
|
14153
15830
|
reply.type("application/vnd.android.package-archive");
|
|
14154
|
-
return reply.send(await
|
|
15831
|
+
return reply.send(await fs9.readFile(download.filePath));
|
|
14155
15832
|
} catch (error) {
|
|
14156
15833
|
reply.code(409);
|
|
14157
15834
|
return {
|
|
@@ -14214,8 +15891,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14214
15891
|
agent_id: registered.agent.id,
|
|
14215
15892
|
heartbeat_interval_ms: HUB_AGENT_HEARTBEAT_INTERVAL_MS,
|
|
14216
15893
|
heartbeat_timeout_ms: HUB_AGENT_HEARTBEAT_TIMEOUT_MS,
|
|
14217
|
-
registered_at: registered.agent.registered_at ??
|
|
14218
|
-
received_at: registered.agent.last_seen_at ??
|
|
15894
|
+
registered_at: registered.agent.registered_at ?? isoNow6(),
|
|
15895
|
+
received_at: registered.agent.last_seen_at ?? isoNow6()
|
|
14219
15896
|
};
|
|
14220
15897
|
});
|
|
14221
15898
|
app.post("/api/agents/heartbeat", async (request, reply) => {
|
|
@@ -14240,8 +15917,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14240
15917
|
agent_id: registered.agent.id,
|
|
14241
15918
|
heartbeat_interval_ms: HUB_AGENT_HEARTBEAT_INTERVAL_MS,
|
|
14242
15919
|
heartbeat_timeout_ms: HUB_AGENT_HEARTBEAT_TIMEOUT_MS,
|
|
14243
|
-
registered_at: registered.agent.registered_at ??
|
|
14244
|
-
received_at: registered.agent.last_seen_at ??
|
|
15920
|
+
registered_at: registered.agent.registered_at ?? isoNow6(),
|
|
15921
|
+
received_at: registered.agent.last_seen_at ?? isoNow6()
|
|
14245
15922
|
};
|
|
14246
15923
|
});
|
|
14247
15924
|
app.post(
|
|
@@ -14335,10 +16012,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14335
16012
|
return { error: "Invalid Web Push test payload." };
|
|
14336
16013
|
}
|
|
14337
16014
|
const delivered = await deliverWebPushToStoredSubscription(parsed.data.endpoint, {
|
|
14338
|
-
title: "Panda
|
|
14339
|
-
body: "
|
|
16015
|
+
title: "Panda test push",
|
|
16016
|
+
body: "This is a test notification from Hub.",
|
|
14340
16017
|
url: "/",
|
|
14341
|
-
tag:
|
|
16018
|
+
tag: "panda-web-push-test:" + Date.now()
|
|
14342
16019
|
});
|
|
14343
16020
|
if (!delivered.ok) {
|
|
14344
16021
|
reply.code(delivered.error === "Web Push subscription not found." ? 404 : 502);
|
|
@@ -14695,7 +16372,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14695
16372
|
const targetPath = ensureGitActionPath(project.path, request.body.path);
|
|
14696
16373
|
const targetFile = workspace.files.find((file) => file.path === targetPath) ?? null;
|
|
14697
16374
|
if (!targetFile) {
|
|
14698
|
-
throw new Error("
|
|
16375
|
+
throw new Error("Target file has no pending changes.");
|
|
14699
16376
|
}
|
|
14700
16377
|
await discardGitWorkspaceFile(project.path, targetFile);
|
|
14701
16378
|
} else if (request.body.action === "discard-all") {
|
|
@@ -14706,16 +16383,16 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14706
16383
|
const workspace = await readSessionGitWorkspace(session, project);
|
|
14707
16384
|
const targetBranch = request.body.branch?.trim() ?? "";
|
|
14708
16385
|
if (!targetBranch) {
|
|
14709
|
-
throw new Error("
|
|
16386
|
+
throw new Error("Please select a branch.");
|
|
14710
16387
|
}
|
|
14711
16388
|
if (workspace.branch === targetBranch) {
|
|
14712
|
-
throw new Error("
|
|
16389
|
+
throw new Error("Already on this branch.");
|
|
14713
16390
|
}
|
|
14714
16391
|
if (workspace.files.length > 0) {
|
|
14715
|
-
throw new Error("
|
|
16392
|
+
throw new Error("Commit or discard current changes before switching branches.");
|
|
14716
16393
|
}
|
|
14717
16394
|
if (!workspace.branches.includes(targetBranch)) {
|
|
14718
|
-
throw new Error("
|
|
16395
|
+
throw new Error("Target branch does not exist.");
|
|
14719
16396
|
}
|
|
14720
16397
|
await switchGitWorkspaceBranch(project.path, targetBranch);
|
|
14721
16398
|
} else if (request.body.action === "push") {
|
|
@@ -14817,7 +16494,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14817
16494
|
});
|
|
14818
16495
|
const generation = parseGeneratedRunCommandDrafts(raw);
|
|
14819
16496
|
if (generation.commands.length === 0) {
|
|
14820
|
-
throw new Error("Codex
|
|
16497
|
+
throw new Error("Codex did not generate any savable project commands.");
|
|
14821
16498
|
}
|
|
14822
16499
|
const catalog = await replaceGeneratedProjectRunCommands({
|
|
14823
16500
|
sessionId,
|
|
@@ -14905,7 +16582,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14905
16582
|
const generation = parseGeneratedRunWebsiteDrafts(raw);
|
|
14906
16583
|
const normalizedWebsites = normalizeGeneratedWebsiteDrafts(generation.websites, request);
|
|
14907
16584
|
if (normalizedWebsites.length === 0) {
|
|
14908
|
-
throw new Error("Codex
|
|
16585
|
+
throw new Error("Codex did not generate any savable website URLs.");
|
|
14909
16586
|
}
|
|
14910
16587
|
const catalog = await replaceGeneratedProjectRunWebsites({
|
|
14911
16588
|
sessionId,
|
|
@@ -14947,7 +16624,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14947
16624
|
if (request.body.action === "run-command" || request.body.action === "run-kill-command") {
|
|
14948
16625
|
const commandId = request.body.commandId?.trim() ?? "";
|
|
14949
16626
|
if (!commandId) {
|
|
14950
|
-
throw new Error("
|
|
16627
|
+
throw new Error("Please select a command to run.");
|
|
14951
16628
|
}
|
|
14952
16629
|
const catalog = await readProjectRunCommandCatalog({
|
|
14953
16630
|
sessionId,
|
|
@@ -14956,12 +16633,12 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14956
16633
|
});
|
|
14957
16634
|
const command = catalog.commands.find((item) => item.id === commandId);
|
|
14958
16635
|
if (!command) {
|
|
14959
|
-
throw new Error("
|
|
16636
|
+
throw new Error("Target command does not exist.");
|
|
14960
16637
|
}
|
|
14961
16638
|
const isKillAction = request.body.action === "run-kill-command";
|
|
14962
16639
|
const killCommand = command.kill_command?.trim() ?? "";
|
|
14963
16640
|
if (isKillAction && !killCommand) {
|
|
14964
|
-
throw new Error("
|
|
16641
|
+
throw new Error("This command has no executable stop command.");
|
|
14965
16642
|
}
|
|
14966
16643
|
const resolved = await resolveRunCommandExecution(project.path, command, {
|
|
14967
16644
|
overrideCommand: isKillAction ? killCommand : null
|
|
@@ -14970,7 +16647,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14970
16647
|
sessionId,
|
|
14971
16648
|
projectId: project.id,
|
|
14972
16649
|
commandId: command.id,
|
|
14973
|
-
title: isKillAction ?
|
|
16650
|
+
title: isKillAction ? command.name + " - Stop" : command.name,
|
|
14974
16651
|
command: resolved.command,
|
|
14975
16652
|
cwd: resolved.cwd,
|
|
14976
16653
|
env: resolved.env,
|
|
@@ -14981,7 +16658,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
14981
16658
|
}
|
|
14982
16659
|
const terminalId = request.body.terminalId?.trim() ?? "";
|
|
14983
16660
|
if (!terminalId) {
|
|
14984
|
-
throw new Error("
|
|
16661
|
+
throw new Error("Missing target terminal.");
|
|
14985
16662
|
}
|
|
14986
16663
|
if (request.body.action === "stop") {
|
|
14987
16664
|
return {
|
|
@@ -15117,6 +16794,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15117
16794
|
return readWorkspaceSessionPage({
|
|
15118
16795
|
bucket,
|
|
15119
16796
|
projectId: query.projectId ?? null,
|
|
16797
|
+
projectless: query.projectless === "1" || query.projectless === "true",
|
|
15120
16798
|
cursor: query.cursor,
|
|
15121
16799
|
limit: query.limit,
|
|
15122
16800
|
selectedSessionId: query.selectedSessionId ?? null
|
|
@@ -15211,7 +16889,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15211
16889
|
return { project: existing, created: false };
|
|
15212
16890
|
}
|
|
15213
16891
|
const project = {
|
|
15214
|
-
id:
|
|
16892
|
+
id: "project-" + slugify(name) + "-" + Date.now(),
|
|
15215
16893
|
agent_id: localAgentId,
|
|
15216
16894
|
name,
|
|
15217
16895
|
display_name: null,
|
|
@@ -15247,6 +16925,45 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15247
16925
|
return null;
|
|
15248
16926
|
}
|
|
15249
16927
|
};
|
|
16928
|
+
const ensureProjectlessConversationDirectory = async (title) => {
|
|
16929
|
+
const slugBase = slugify(title) || "chat";
|
|
16930
|
+
const targetPath = path11.join(
|
|
16931
|
+
PROJECTLESS_DIRECTORY_ROOT,
|
|
16932
|
+
slugBase + "-" + Date.now()
|
|
16933
|
+
);
|
|
16934
|
+
await fs9.mkdir(targetPath, { recursive: true });
|
|
16935
|
+
return targetPath;
|
|
16936
|
+
};
|
|
16937
|
+
const buildManagedSession = (input) => {
|
|
16938
|
+
const projectless = input.projectless === true || !input.project;
|
|
16939
|
+
const project = input.project ?? null;
|
|
16940
|
+
const isRunning = input.messageInput.length > 0 || input.attachments.length > 0;
|
|
16941
|
+
return {
|
|
16942
|
+
id: input.sessionId,
|
|
16943
|
+
agent_id: localAgentId,
|
|
16944
|
+
project_id: project?.id ?? PROJECTLESS_SESSION_PROJECT_ID,
|
|
16945
|
+
provider: "codex",
|
|
16946
|
+
archived: false,
|
|
16947
|
+
title: input.title,
|
|
16948
|
+
mode: "managed",
|
|
16949
|
+
health: "active",
|
|
16950
|
+
branch: project?.branch ?? "unknown",
|
|
16951
|
+
worktree: project?.worktree ?? PROJECTLESS_WORKTREE,
|
|
16952
|
+
summary: input.messageInput ? truncateSummary(input.messageInput) : input.attachments[0]?.name ?? "",
|
|
16953
|
+
latest_assistant_message: null,
|
|
16954
|
+
last_event_at: input.sessionTimestamp,
|
|
16955
|
+
pinned: false,
|
|
16956
|
+
run_state: isRunning ? "running" : "idle",
|
|
16957
|
+
run_state_changed_at: isRunning ? input.sessionTimestamp : null,
|
|
16958
|
+
context_usage: null,
|
|
16959
|
+
subagent: null,
|
|
16960
|
+
capability: buildManagedSessionCapability({
|
|
16961
|
+
archived: false,
|
|
16962
|
+
projectless,
|
|
16963
|
+
live: true
|
|
16964
|
+
})
|
|
16965
|
+
};
|
|
16966
|
+
};
|
|
15250
16967
|
app.post("/api/sessions", async (request, reply) => {
|
|
15251
16968
|
const title = request.body.title.trim();
|
|
15252
16969
|
const input = request.body.input?.trim() ?? "";
|
|
@@ -15298,7 +17015,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15298
17015
|
codexConfig: configDiagnostics
|
|
15299
17016
|
}, "Forwarding Panda session creation to Codex.");
|
|
15300
17017
|
let sessionId = "";
|
|
15301
|
-
const sessionTimestamp =
|
|
17018
|
+
const sessionTimestamp = isoNow6();
|
|
15302
17019
|
try {
|
|
15303
17020
|
const created = await liveSessionStream.startThread({
|
|
15304
17021
|
cwd: project.path,
|
|
@@ -15342,35 +17059,14 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15342
17059
|
request: requestDiagnostics,
|
|
15343
17060
|
codexConfig: configDiagnostics
|
|
15344
17061
|
}, "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,
|
|
17062
|
+
const session = buildManagedSession({
|
|
17063
|
+
sessionId,
|
|
15351
17064
|
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
|
-
};
|
|
17065
|
+
messageInput: input,
|
|
17066
|
+
attachments,
|
|
17067
|
+
sessionTimestamp,
|
|
17068
|
+
project
|
|
17069
|
+
});
|
|
15374
17070
|
setManagedSessionActive(session.id);
|
|
15375
17071
|
managedSessions.set(session.id, session);
|
|
15376
17072
|
upsertSnapshotSession(session);
|
|
@@ -15378,6 +17074,126 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15378
17074
|
void generateSessionTitleInBackground({
|
|
15379
17075
|
sessionId: session.id,
|
|
15380
17076
|
project,
|
|
17077
|
+
cwd: project.path,
|
|
17078
|
+
fallbackTitle: title,
|
|
17079
|
+
message: input,
|
|
17080
|
+
attachments,
|
|
17081
|
+
model: titleGenerationModel
|
|
17082
|
+
});
|
|
17083
|
+
reply.code(201);
|
|
17084
|
+
return { session };
|
|
17085
|
+
});
|
|
17086
|
+
app.post("/api/chats", async (request, reply) => {
|
|
17087
|
+
const title = request.body.title.trim();
|
|
17088
|
+
const input = request.body.input?.trim() ?? "";
|
|
17089
|
+
const attachments = normalizeSessionInputAttachments(request.body.attachments);
|
|
17090
|
+
const model = request.body.model?.trim() || null;
|
|
17091
|
+
const titleGenerationModel = request.body.titleGenerationModel?.trim() || DEFAULT_SESSION_TITLE_GENERATION_MODEL;
|
|
17092
|
+
const reasoningEffort = request.body.reasoningEffort?.trim() || null;
|
|
17093
|
+
const requestedServiceTier = request.body.serviceTier;
|
|
17094
|
+
const normalizedRequestedServiceTier = normalizeServiceTier(requestedServiceTier);
|
|
17095
|
+
const serviceTier = resolvePandaServiceTier(normalizedRequestedServiceTier);
|
|
17096
|
+
const planMode = request.body.planMode === true;
|
|
17097
|
+
const yoloMode = request.body.yoloMode === true;
|
|
17098
|
+
if (!title) {
|
|
17099
|
+
reply.code(400);
|
|
17100
|
+
return { error: "Session title is required." };
|
|
17101
|
+
}
|
|
17102
|
+
if (!input && attachments.length === 0) {
|
|
17103
|
+
reply.code(400);
|
|
17104
|
+
return { error: "Message input or attachments are required." };
|
|
17105
|
+
}
|
|
17106
|
+
const requestDiagnostics = summarizeForwardingRequest(request);
|
|
17107
|
+
const prompt = buildTurnPrompt(input, planMode) || null;
|
|
17108
|
+
const payloadDiagnostics = summarizeForwardingPayload({
|
|
17109
|
+
title,
|
|
17110
|
+
input,
|
|
17111
|
+
prompt,
|
|
17112
|
+
attachments,
|
|
17113
|
+
model,
|
|
17114
|
+
titleGenerationModel,
|
|
17115
|
+
reasoningEffort,
|
|
17116
|
+
requestedServiceTier,
|
|
17117
|
+
normalizedRequestedServiceTier,
|
|
17118
|
+
effectiveServiceTier: serviceTier,
|
|
17119
|
+
planMode,
|
|
17120
|
+
yoloMode
|
|
17121
|
+
});
|
|
17122
|
+
const projectlessPath = await ensureProjectlessConversationDirectory(title);
|
|
17123
|
+
const configDiagnostics = await readCodexConfigDiagnostics(projectlessPath);
|
|
17124
|
+
diagnosticLogger.info({
|
|
17125
|
+
route: "create-chat",
|
|
17126
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17127
|
+
projectPath: projectlessPath,
|
|
17128
|
+
payload: payloadDiagnostics,
|
|
17129
|
+
request: requestDiagnostics,
|
|
17130
|
+
codexConfig: configDiagnostics
|
|
17131
|
+
}, "Forwarding Panda projectless chat creation to Codex.");
|
|
17132
|
+
let sessionId = "";
|
|
17133
|
+
const sessionTimestamp = isoNow6();
|
|
17134
|
+
try {
|
|
17135
|
+
const created = await liveSessionStream.startThread({
|
|
17136
|
+
cwd: projectlessPath,
|
|
17137
|
+
title,
|
|
17138
|
+
prompt,
|
|
17139
|
+
attachments,
|
|
17140
|
+
model,
|
|
17141
|
+
reasoningEffort,
|
|
17142
|
+
serviceTier,
|
|
17143
|
+
yoloMode
|
|
17144
|
+
});
|
|
17145
|
+
sessionId = created.sessionId;
|
|
17146
|
+
await Promise.all([
|
|
17147
|
+
appendSessionIndexUpdate(
|
|
17148
|
+
sessionId,
|
|
17149
|
+
{
|
|
17150
|
+
thread_name: title,
|
|
17151
|
+
updated_at: sessionTimestamp
|
|
17152
|
+
},
|
|
17153
|
+
codexHome
|
|
17154
|
+
),
|
|
17155
|
+
registerProjectlessThread(sessionId, PROJECTLESS_DIRECTORY_ROOT, codexHome)
|
|
17156
|
+
]);
|
|
17157
|
+
} catch (error) {
|
|
17158
|
+
diagnosticLogger.error({
|
|
17159
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
17160
|
+
route: "create-chat",
|
|
17161
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17162
|
+
projectPath: projectlessPath,
|
|
17163
|
+
payload: payloadDiagnostics,
|
|
17164
|
+
request: requestDiagnostics,
|
|
17165
|
+
codexConfig: configDiagnostics
|
|
17166
|
+
}, "Failed to create Codex projectless chat from Panda.");
|
|
17167
|
+
reply.code(502);
|
|
17168
|
+
return {
|
|
17169
|
+
error: error instanceof Error ? error.message : "Unable to start Codex chat."
|
|
17170
|
+
};
|
|
17171
|
+
}
|
|
17172
|
+
diagnosticLogger.info({
|
|
17173
|
+
route: "create-chat",
|
|
17174
|
+
sessionId,
|
|
17175
|
+
projectId: PROJECTLESS_SESSION_PROJECT_ID,
|
|
17176
|
+
projectPath: projectlessPath,
|
|
17177
|
+
payload: payloadDiagnostics,
|
|
17178
|
+
request: requestDiagnostics,
|
|
17179
|
+
codexConfig: configDiagnostics
|
|
17180
|
+
}, "Successfully created Codex projectless chat from Panda.");
|
|
17181
|
+
const session = buildManagedSession({
|
|
17182
|
+
sessionId,
|
|
17183
|
+
title,
|
|
17184
|
+
messageInput: input,
|
|
17185
|
+
attachments,
|
|
17186
|
+
sessionTimestamp,
|
|
17187
|
+
projectless: true
|
|
17188
|
+
});
|
|
17189
|
+
setManagedSessionActive(session.id);
|
|
17190
|
+
managedSessions.set(session.id, session);
|
|
17191
|
+
upsertSnapshotSession(session);
|
|
17192
|
+
broadcastSnapshotChanged();
|
|
17193
|
+
void generateSessionTitleInBackground({
|
|
17194
|
+
sessionId: session.id,
|
|
17195
|
+
project: null,
|
|
17196
|
+
cwd: projectlessPath,
|
|
15381
17197
|
fallbackTitle: title,
|
|
15382
17198
|
message: input,
|
|
15383
17199
|
attachments,
|
|
@@ -15386,6 +17202,196 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15386
17202
|
reply.code(201);
|
|
15387
17203
|
return { session };
|
|
15388
17204
|
});
|
|
17205
|
+
app.get(
|
|
17206
|
+
"/api/image-provider/settings",
|
|
17207
|
+
async () => buildResolvedImageProviderSettings({
|
|
17208
|
+
activeChannelId: imageSessionStore.getSettings().active_channel_id,
|
|
17209
|
+
channels: imageSessionStore.getSettings().channels.map((channel) => ({
|
|
17210
|
+
id: channel.id,
|
|
17211
|
+
remark: channel.remark,
|
|
17212
|
+
baseUrl: channel.base_url,
|
|
17213
|
+
storedApiKey: channel.api_key,
|
|
17214
|
+
models: channel.models,
|
|
17215
|
+
updatedAt: channel.updated_at
|
|
17216
|
+
})),
|
|
17217
|
+
updatedAt: imageSessionStore.getSettings().updated_at
|
|
17218
|
+
})
|
|
17219
|
+
);
|
|
17220
|
+
app.post("/api/image-provider/settings", async (request, reply) => {
|
|
17221
|
+
const parsed = imageProviderSettingsUpdateSchema.safeParse(request.body ?? {});
|
|
17222
|
+
if (!parsed.success) {
|
|
17223
|
+
reply.code(400);
|
|
17224
|
+
return { error: "Invalid image provider settings payload." };
|
|
17225
|
+
}
|
|
17226
|
+
const saved = await imageSessionStore.updateSettings({
|
|
17227
|
+
activeChannelId: parsed.data.active_channel_id,
|
|
17228
|
+
channels: parsed.data.channels.map((channel) => ({
|
|
17229
|
+
id: channel.id,
|
|
17230
|
+
remark: channel.remark,
|
|
17231
|
+
baseUrl: normalizeImageProviderBaseUrl(channel.base_url),
|
|
17232
|
+
models: channel.models,
|
|
17233
|
+
apiKeyMode: channel.api_key_mode,
|
|
17234
|
+
apiKey: channel.api_key ?? null
|
|
17235
|
+
}))
|
|
17236
|
+
});
|
|
17237
|
+
return buildResolvedImageProviderSettings({
|
|
17238
|
+
activeChannelId: saved.active_channel_id,
|
|
17239
|
+
channels: saved.channels.map((channel) => ({
|
|
17240
|
+
id: channel.id,
|
|
17241
|
+
remark: channel.remark,
|
|
17242
|
+
baseUrl: channel.base_url,
|
|
17243
|
+
storedApiKey: channel.api_key,
|
|
17244
|
+
models: channel.models,
|
|
17245
|
+
updatedAt: channel.updated_at
|
|
17246
|
+
})),
|
|
17247
|
+
updatedAt: saved.updated_at
|
|
17248
|
+
});
|
|
17249
|
+
});
|
|
17250
|
+
app.post("/api/image-provider/models", async (request, reply) => {
|
|
17251
|
+
const parsed = imageProviderModelListRequestSchema.safeParse(request.body ?? {});
|
|
17252
|
+
if (!parsed.success) {
|
|
17253
|
+
reply.code(400);
|
|
17254
|
+
return { error: "Invalid image provider model list payload." };
|
|
17255
|
+
}
|
|
17256
|
+
const storedChannel = parsed.data.channel_id ? imageSessionStore.getSettings().channels.find((channel) => channel.id === parsed.data.channel_id) : null;
|
|
17257
|
+
const baseUrl = normalizeImageProviderBaseUrl(parsed.data.base_url);
|
|
17258
|
+
const requestApiKey = parsed.data.api_key_mode === "set" ? parsed.data.api_key?.trim() || "" : parsed.data.api_key_mode === "clear" ? "" : storedChannel?.api_key?.trim() || "";
|
|
17259
|
+
const apiKey = requestApiKey || resolveImageProviderApiKey();
|
|
17260
|
+
if (!apiKey) {
|
|
17261
|
+
reply.code(400);
|
|
17262
|
+
return { error: "\u672A\u68C0\u6D4B\u5230\u56FE\u7247\u63A5\u53E3 API Key\u3002" };
|
|
17263
|
+
}
|
|
17264
|
+
return imageProviderModelListResponseSchema.parse({
|
|
17265
|
+
models: await listImageProviderModels({ baseUrl, apiKey })
|
|
17266
|
+
});
|
|
17267
|
+
});
|
|
17268
|
+
app.post("/api/image-provider/test", async (request) => {
|
|
17269
|
+
const parsed = imageProviderSettingsUpdateSchema.safeParse(request.body ?? {});
|
|
17270
|
+
const storedSettings = imageSessionStore.getSettings();
|
|
17271
|
+
const requestSettings = parsed.success ? parsed.data : {
|
|
17272
|
+
active_channel_id: storedSettings.active_channel_id,
|
|
17273
|
+
channels: storedSettings.channels.map((channel) => ({
|
|
17274
|
+
id: channel.id,
|
|
17275
|
+
remark: channel.remark,
|
|
17276
|
+
base_url: channel.base_url,
|
|
17277
|
+
api_key: null,
|
|
17278
|
+
api_key_mode: "keep",
|
|
17279
|
+
models: channel.models
|
|
17280
|
+
}))
|
|
17281
|
+
};
|
|
17282
|
+
const testedChannelId = requestSettings.channels.some(
|
|
17283
|
+
(channel) => channel.id === requestSettings.active_channel_id
|
|
17284
|
+
) ? requestSettings.active_channel_id : requestSettings.channels[0]?.id ?? null;
|
|
17285
|
+
const testedChannel = requestSettings.channels.find(
|
|
17286
|
+
(channel) => channel.id === testedChannelId
|
|
17287
|
+
);
|
|
17288
|
+
const storedChannel = storedSettings.channels.find(
|
|
17289
|
+
(channel) => channel.id === testedChannelId
|
|
17290
|
+
);
|
|
17291
|
+
const baseUrl = normalizeImageProviderBaseUrl(testedChannel?.base_url);
|
|
17292
|
+
const storedApiKey = storedChannel?.api_key?.trim() || "";
|
|
17293
|
+
const requestApiKey = testedChannel?.api_key_mode === "set" ? testedChannel.api_key?.trim() || "" : testedChannel?.api_key_mode === "clear" ? "" : storedApiKey;
|
|
17294
|
+
const apiKey = requestApiKey || resolveImageProviderApiKey();
|
|
17295
|
+
if (!apiKey) {
|
|
17296
|
+
return {
|
|
17297
|
+
ok: false,
|
|
17298
|
+
message: "No image API key found. Configure one in settings or environment variables.",
|
|
17299
|
+
model_available: false,
|
|
17300
|
+
resolved_base_url: baseUrl
|
|
17301
|
+
};
|
|
17302
|
+
}
|
|
17303
|
+
try {
|
|
17304
|
+
const result = await testImageProviderConnection({
|
|
17305
|
+
baseUrl,
|
|
17306
|
+
apiKey,
|
|
17307
|
+
model: getDefaultImageProviderModel(),
|
|
17308
|
+
vendor: "openai"
|
|
17309
|
+
});
|
|
17310
|
+
return {
|
|
17311
|
+
ok: true,
|
|
17312
|
+
message: result.modelAvailable ? "Connection succeeded and gpt-image-2 was found." : "Connection succeeded but gpt-image-2 was not found.",
|
|
17313
|
+
model_available: result.modelAvailable,
|
|
17314
|
+
resolved_base_url: baseUrl
|
|
17315
|
+
};
|
|
17316
|
+
} catch (error) {
|
|
17317
|
+
return {
|
|
17318
|
+
ok: false,
|
|
17319
|
+
message: error instanceof Error ? error.message : "Image provider test failed.",
|
|
17320
|
+
model_available: false,
|
|
17321
|
+
resolved_base_url: baseUrl
|
|
17322
|
+
};
|
|
17323
|
+
}
|
|
17324
|
+
});
|
|
17325
|
+
app.post("/api/image-sessions", async (request, reply) => {
|
|
17326
|
+
if (mode === "hub") {
|
|
17327
|
+
reply.code(409);
|
|
17328
|
+
return { error: "Image sessions can only be created on agent." };
|
|
17329
|
+
}
|
|
17330
|
+
const parsed = imageSessionCreateRequestSchema.safeParse(request.body ?? {});
|
|
17331
|
+
if (!parsed.success) {
|
|
17332
|
+
reply.code(400);
|
|
17333
|
+
return { error: "Invalid image session payload." };
|
|
17334
|
+
}
|
|
17335
|
+
const record = await imageSessionStore.createSession(localAgentId, parsed.data.title ?? null);
|
|
17336
|
+
syncImageSessionSnapshot(record.session.id);
|
|
17337
|
+
reply.code(201);
|
|
17338
|
+
return { session: record.session };
|
|
17339
|
+
});
|
|
17340
|
+
app.get("/api/image-sessions/:sessionId", async (request, reply) => {
|
|
17341
|
+
const { sessionId } = request.params;
|
|
17342
|
+
const detail = await readImageSessionDetail(sessionId);
|
|
17343
|
+
if (!detail) {
|
|
17344
|
+
reply.code(404);
|
|
17345
|
+
return { error: "Image session not found." };
|
|
17346
|
+
}
|
|
17347
|
+
return detail;
|
|
17348
|
+
});
|
|
17349
|
+
app.post("/api/image-sessions/:sessionId/submit", async (request, reply) => {
|
|
17350
|
+
const { sessionId } = request.params;
|
|
17351
|
+
const parsed = imageSessionSubmitRequestSchema.safeParse(request.body ?? {});
|
|
17352
|
+
if (!parsed.success) {
|
|
17353
|
+
reply.code(400);
|
|
17354
|
+
return { error: "Invalid image session payload." };
|
|
17355
|
+
}
|
|
17356
|
+
const prompt = parsed.data.prompt.trim();
|
|
17357
|
+
if (!prompt) {
|
|
17358
|
+
reply.code(400);
|
|
17359
|
+
return { error: "Prompt is required." };
|
|
17360
|
+
}
|
|
17361
|
+
try {
|
|
17362
|
+
return await submitImageSessionPrompt({
|
|
17363
|
+
sessionId,
|
|
17364
|
+
prompt,
|
|
17365
|
+
appendToTurnId: parsed.data.append_to_turn_id,
|
|
17366
|
+
sources: parsed.data.sources,
|
|
17367
|
+
model: parsed.data.model,
|
|
17368
|
+
quality: parsed.data.quality,
|
|
17369
|
+
size: parsed.data.size,
|
|
17370
|
+
inputFidelity: parsed.data.input_fidelity,
|
|
17371
|
+
aspectRatio: parsed.data.aspect_ratio,
|
|
17372
|
+
outputFormat: parsed.data.output_format,
|
|
17373
|
+
outputCompression: parsed.data.output_compression,
|
|
17374
|
+
background: parsed.data.background,
|
|
17375
|
+
moderation: parsed.data.moderation,
|
|
17376
|
+
n: parsed.data.n
|
|
17377
|
+
});
|
|
17378
|
+
} catch (error) {
|
|
17379
|
+
reply.code(409);
|
|
17380
|
+
return {
|
|
17381
|
+
error: error instanceof Error ? error.message : "Unable to submit image session prompt."
|
|
17382
|
+
};
|
|
17383
|
+
}
|
|
17384
|
+
});
|
|
17385
|
+
app.get("/api/image-assets/:assetId", async (request, reply) => {
|
|
17386
|
+
const { assetId } = request.params;
|
|
17387
|
+
const found = findImageAssetRecordById(assetId);
|
|
17388
|
+
if (!found) {
|
|
17389
|
+
reply.code(404);
|
|
17390
|
+
return { error: "Image asset not found." };
|
|
17391
|
+
}
|
|
17392
|
+
reply.type(found.asset.mime_type ?? "application/octet-stream");
|
|
17393
|
+
return reply.send(await fs9.readFile(imageSessionStore.resolveAssetAbsolutePath(found.asset)));
|
|
17394
|
+
});
|
|
15389
17395
|
app.post(
|
|
15390
17396
|
"/api/sessions/:sessionId/actions",
|
|
15391
17397
|
async (request, reply) => {
|
|
@@ -15417,6 +17423,49 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15417
17423
|
reply.code(409);
|
|
15418
17424
|
return { error: "Running sessions cannot be changed right now." };
|
|
15419
17425
|
}
|
|
17426
|
+
if (session.provider === "image") {
|
|
17427
|
+
if (action === "pin" || action === "unpin") {
|
|
17428
|
+
const pinned = action === "pin";
|
|
17429
|
+
await imageSessionStore.mutateSessionRef(sessionId, (current) => ({
|
|
17430
|
+
...current,
|
|
17431
|
+
pinned
|
|
17432
|
+
}));
|
|
17433
|
+
syncImageSessionSnapshot(sessionId);
|
|
17434
|
+
broadcastEvent("session.updated", {
|
|
17435
|
+
sessionId,
|
|
17436
|
+
sessionPatch: {
|
|
17437
|
+
pinned
|
|
17438
|
+
}
|
|
17439
|
+
});
|
|
17440
|
+
return {
|
|
17441
|
+
ok: true,
|
|
17442
|
+
affectedSessionIds: [sessionId],
|
|
17443
|
+
nextSessionId: findNextSessionId(snapshot.sessions)
|
|
17444
|
+
};
|
|
17445
|
+
}
|
|
17446
|
+
if (action === "archive") {
|
|
17447
|
+
await imageSessionStore.mutateSessionRef(sessionId, (current) => ({
|
|
17448
|
+
...current,
|
|
17449
|
+
archived: true,
|
|
17450
|
+
health: "offline",
|
|
17451
|
+
run_state: "idle",
|
|
17452
|
+
run_state_changed_at: null,
|
|
17453
|
+
capability: {
|
|
17454
|
+
...current.capability,
|
|
17455
|
+
can_send_input: false
|
|
17456
|
+
}
|
|
17457
|
+
}));
|
|
17458
|
+
syncImageSessionSnapshot(sessionId);
|
|
17459
|
+
} else {
|
|
17460
|
+
await imageSessionStore.deleteSession(sessionId);
|
|
17461
|
+
removeSnapshotSessions([sessionId]);
|
|
17462
|
+
}
|
|
17463
|
+
return {
|
|
17464
|
+
ok: true,
|
|
17465
|
+
affectedSessionIds: [sessionId],
|
|
17466
|
+
nextSessionId: findNextSessionId(snapshot.sessions)
|
|
17467
|
+
};
|
|
17468
|
+
}
|
|
15420
17469
|
const affectedSessionIds = [
|
|
15421
17470
|
sessionId,
|
|
15422
17471
|
...session.subagent ? [] : collectDescendantSessionIds(sessionId, snapshot.sessions)
|
|
@@ -15827,8 +17876,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15827
17876
|
codexConfig: configDiagnostics
|
|
15828
17877
|
}, "Successfully forwarded Panda session input to Codex.");
|
|
15829
17878
|
setManagedSessionActive(sessionId);
|
|
15830
|
-
const runningAt =
|
|
15831
|
-
const summary = input ? truncateSummary(input) : attachments[0]?.name ?? "\
|
|
17879
|
+
const runningAt = isoNow6();
|
|
17880
|
+
const summary = input ? truncateSummary(input) : attachments[0]?.name ?? "\u95C4\u52EA\u6B22";
|
|
15832
17881
|
const overlayAttachments = buildInlineTimelineAttachments(attachments);
|
|
15833
17882
|
managedSessions.set(sessionId, {
|
|
15834
17883
|
...toSessionWithRunState(session, {
|
|
@@ -15853,9 +17902,9 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15853
17902
|
latest_assistant_message: null
|
|
15854
17903
|
});
|
|
15855
17904
|
const overlayUserEntry = {
|
|
15856
|
-
id:
|
|
17905
|
+
id: USER_OVERLAY_ENTRY_PREFIX + sessionId + ":" + Date.now(),
|
|
15857
17906
|
kind: "user",
|
|
15858
|
-
title: "
|
|
17907
|
+
title: "You",
|
|
15859
17908
|
body: input,
|
|
15860
17909
|
body_truncated: false,
|
|
15861
17910
|
detail_available: false,
|
|
@@ -15901,7 +17950,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15901
17950
|
};
|
|
15902
17951
|
}
|
|
15903
17952
|
setManagedSessionActive(sessionId);
|
|
15904
|
-
const completedAt =
|
|
17953
|
+
const completedAt = isoNow6();
|
|
15905
17954
|
const previousSessionState = {
|
|
15906
17955
|
run_state: session.run_state,
|
|
15907
17956
|
run_state_changed_at: session.run_state_changed_at,
|
|
@@ -15937,10 +17986,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15937
17986
|
}
|
|
15938
17987
|
});
|
|
15939
17988
|
const interruptEntry = {
|
|
15940
|
-
id:
|
|
17989
|
+
id: "entry-system-interrupt-" + Date.now(),
|
|
15941
17990
|
kind: "system",
|
|
15942
|
-
title: "
|
|
15943
|
-
body: "
|
|
17991
|
+
title: "Interrupt requested",
|
|
17992
|
+
body: "Interrupt request submitted.",
|
|
15944
17993
|
body_truncated: false,
|
|
15945
17994
|
detail_available: false,
|
|
15946
17995
|
patch_summary: null,
|
|
@@ -15976,7 +18025,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
15976
18025
|
await liveSessionStream.ensureSessionTracker(sessionId, discoveredSessionFiles[sessionId]);
|
|
15977
18026
|
await liveSessionStream.interruptTurn(sessionId);
|
|
15978
18027
|
} catch (error) {
|
|
15979
|
-
const failedAt =
|
|
18028
|
+
const failedAt = isoNow6();
|
|
15980
18029
|
const failureMessage = error instanceof Error ? error.message : "Unable to interrupt Codex session.";
|
|
15981
18030
|
const nextRunState = previousSessionState.run_state === "running" ? "running" : previousSessionState.run_state;
|
|
15982
18031
|
const nextRunStateChangedAt = nextRunState === "running" ? failedAt : previousSessionState.run_state_changed_at;
|
|
@@ -16010,10 +18059,10 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16010
18059
|
}
|
|
16011
18060
|
});
|
|
16012
18061
|
const failureEntry = {
|
|
16013
|
-
id:
|
|
18062
|
+
id: "entry-system-interrupt-failed-" + Date.now(),
|
|
16014
18063
|
kind: "system",
|
|
16015
|
-
title: "
|
|
16016
|
-
body:
|
|
18064
|
+
title: "Interrupt failed",
|
|
18065
|
+
body: "Interrupt failed: " + failureMessage,
|
|
16017
18066
|
body_truncated: false,
|
|
16018
18067
|
detail_available: false,
|
|
16019
18068
|
patch_summary: null,
|
|
@@ -16209,7 +18258,8 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16209
18258
|
}
|
|
16210
18259
|
sendSocketEvent(client, "timeline.reset", {
|
|
16211
18260
|
sessionId: message.sessionId,
|
|
16212
|
-
entries: bootstrap.timeline.entries
|
|
18261
|
+
entries: bootstrap.timeline.entries,
|
|
18262
|
+
hasEarlierEntries: bootstrap.timeline.has_earlier_entries
|
|
16213
18263
|
});
|
|
16214
18264
|
sendSocketEvent(client, "interaction.reset", {
|
|
16215
18265
|
sessionId: message.sessionId,
|
|
@@ -16254,7 +18304,7 @@ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
|
|
|
16254
18304
|
}
|
|
16255
18305
|
reply.type(asset.contentType);
|
|
16256
18306
|
reply.header("cache-control", asset.cacheControl);
|
|
16257
|
-
return reply.send(await
|
|
18307
|
+
return reply.send(await fs9.readFile(asset.filePath));
|
|
16258
18308
|
});
|
|
16259
18309
|
}
|
|
16260
18310
|
await app.listen({
|
|
@@ -16283,6 +18333,8 @@ export {
|
|
|
16283
18333
|
writeCodexGlobalState,
|
|
16284
18334
|
getSavedWorkspaceRoots,
|
|
16285
18335
|
getWorkspaceRootLabels,
|
|
18336
|
+
getProjectlessThreadIds,
|
|
18337
|
+
getThreadWorkspaceRootHints,
|
|
16286
18338
|
readPandaThreadPrefs,
|
|
16287
18339
|
writePandaThreadPrefs,
|
|
16288
18340
|
getPinnedWorkspaceRoots,
|
|
@@ -16299,6 +18351,7 @@ export {
|
|
|
16299
18351
|
setWorkspaceRootOrder,
|
|
16300
18352
|
setWorkspaceRootLabel,
|
|
16301
18353
|
setWorkspaceRootVisibility,
|
|
18354
|
+
registerProjectlessThread,
|
|
16302
18355
|
isWithinWorkspaceRoot,
|
|
16303
18356
|
normalizeWorkspacePathKey,
|
|
16304
18357
|
sortByStoredWorkspaceOrder,
|