@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.
@@ -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: "panda",
6289
- version: "0.1.0"
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 fs8 from "fs/promises";
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 path9 from "path";
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/timeline-user-dedup.ts
7168
- var normalizeTimelineEntryBody2 = (entry) => entry.body.replace(/\r\n/g, "\n").trim();
7169
- var areAdjacentDuplicateUserEntries = (previous, next, maxSkewMs) => {
7170
- if (!previous || previous.kind !== "user" || next.kind !== "user") {
7171
- return false;
7172
- }
7173
- if (normalizeTimelineEntryBody2(previous) !== normalizeTimelineEntryBody2(next)) {
7174
- return false;
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
- const previousTime = +new Date(previous.timestamp);
7177
- const nextTime = +new Date(next.timestamp);
7178
- if (!Number.isFinite(previousTime) || !Number.isFinite(nextTime)) {
7179
- return true;
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 Math.abs(nextTime - previousTime) <= maxSkewMs;
7424
+ return trimTrailingSlash(trimmed);
7182
7425
  };
7183
- var collapseDuplicateUserEntries = (entries, options) => {
7184
- const maxSkewMs = options?.maxSkewMs ?? 5e3;
7185
- const collapsed = [];
7186
- for (const entry of entries) {
7187
- const previous = collapsed[collapsed.length - 1];
7188
- if (!areAdjacentDuplicateUserEntries(previous, entry, maxSkewMs)) {
7189
- collapsed.push(entry);
7190
- continue;
7191
- }
7192
- collapsed[collapsed.length - 1] = {
7193
- ...entry,
7194
- attachments: mergeTimelineAttachments(previous?.attachments, entry.attachments)
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
- return collapsed;
7198
- };
7455
+ })()
7456
+ });
7457
+ var getDefaultImageProviderModel = () => DEFAULT_IMAGE_PROVIDER_MODEL;
7199
7458
 
7200
- // packages/provider-codex/src/session-run-workbench.ts
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 PANDA_GLOBAL_DIRECTORY_NAME = "Project Workbench Data";
7207
- var RUN_RESOURCES_DIRECTORY_NAME = "run-workbench";
7208
- var RUN_RESOURCE_INDEX_FILE_NAME = "index.json";
7209
- var RUN_COMMANDS_FILE_NAME = "commands.json";
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 buildProjectStorageKey = (projectPath) => createHash3("sha1").update(normalizeProjectPathKey(projectPath)).digest("hex").slice(0, 16);
7223
- var sanitizeStorageName = (value) => value.normalize("NFKC").replace(/[<>:"/\\|?*\u0000-\u001F]/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
7224
- var resolveUserDocumentsDirectory = () => {
7225
- const home = os4.homedir();
7226
- const userProfile = process.env.USERPROFILE?.trim() || home;
7227
- if (process.platform === "win32") {
7228
- const documents = process.env.PUBLIC?.trim() ? path7.join(userProfile, "Documents") : path7.join(userProfile, "Documents");
7229
- return documents;
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
- if (process.platform === "darwin") {
7232
- return path7.join(home, "Documents");
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
- const xdgDocuments = process.env.XDG_DOCUMENTS_DIR?.trim();
7235
- if (xdgDocuments) {
7236
- return xdgDocuments.replace(/^\$HOME\b/, home);
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 path7.join(home, "Documents");
7490
+ return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
7239
7491
  };
7240
- var resolvePandaGlobalStorageRoot = () => path7.join(resolveUserDocumentsDirectory(), PANDA_GLOBAL_DIRECTORY_NAME, RUN_RESOURCES_DIRECTORY_NAME);
7241
- var normalizeStoredPort = (value) => {
7242
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
7243
- return null;
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
- return value;
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 normalizePort = (value) => normalizeStoredPort(value);
7248
- var normalizeUrl = (value) => {
7249
- const trimmed = value?.trim() ?? "";
7250
- if (!trimmed) {
7251
- throw new Error("\u8BF7\u8F93\u5165\u7F51\u9875\u5730\u5740\u3002");
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
- try {
7254
- const parsed = new URL(trimmed);
7255
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
7256
- throw new Error("\u7F51\u9875\u5730\u5740\u53EA\u652F\u6301 http \u6216 https\u3002");
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 parsed.toString();
7259
- } catch {
7260
- throw new Error("\u7F51\u9875\u5730\u5740\u683C\u5F0F\u65E0\u6548\u3002");
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 readStoredJson = async (filePath) => {
7264
- try {
7265
- const raw = await fs6.readFile(filePath, "utf8");
7266
- return JSON.parse(raw);
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 writeStoredJson = async (filePath, payload) => {
7272
- await fs6.mkdir(path7.dirname(filePath), { recursive: true });
7273
- await fs6.writeFile(filePath, `${JSON.stringify(payload, null, 2)}
7274
- `, "utf8");
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 normalizeRelativeCommandCwd = (projectPath, cwd) => {
7277
- const trimmed = cwd?.trim() ?? "";
7278
- if (!trimmed) {
7279
- return null;
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 absoluteCandidate = path7.resolve(projectPath, trimmed);
7282
- const relative = path7.relative(projectPath, absoluteCandidate);
7283
- if (!relative || relative === ".") {
7284
- return null;
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 (relative.startsWith("..") || path7.isAbsolute(relative)) {
7287
- throw new Error("\u547D\u4EE4\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u9879\u76EE\u5185\u3002");
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 relative.split(path7.sep).join("/");
7711
+ return form;
7290
7712
  };
7291
- var resolveCommandAbsoluteCwd = (projectPath, cwd) => {
7292
- const relative = normalizeRelativeCommandCwd(projectPath, cwd);
7293
- return relative ? path7.resolve(projectPath, relative) : projectPath;
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 normalizeNodeVersion = (value) => {
7296
- const trimmed = value?.trim() ?? "";
7297
- if (!trimmed) {
7298
- return null;
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
- const normalized = trimmed.replace(/^v/i, "");
7301
- if (!NODE_VERSION_PATTERN.test(normalized)) {
7302
- throw new Error("Node \u7248\u672C\u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528 x.y.z\u3002");
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
- return normalized;
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 normalizeOptionalCommand = (value) => {
7307
- const trimmed = value?.trim() ?? "";
7308
- return trimmed || null;
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 compareNodeVersionsDesc = (left, right) => {
7311
- const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
7312
- const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
7313
- for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
7314
- const leftPart = leftParts[index] ?? 0;
7315
- const rightPart = rightParts[index] ?? 0;
7316
- if (leftPart !== rightPart) {
7317
- return rightPart - leftPart;
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 dedupeNodeVersions = (versions) => [
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 path7.join(configuredRoot, "settings.txt");
8653
+ return path9.join(configuredRoot, "settings.txt");
7331
8654
  }
7332
8655
  const appData = process.env.AppData?.trim();
7333
8656
  if (appData) {
7334
- return path7.join(appData, "nvm", "settings.txt");
8657
+ return path9.join(appData, "nvm", "settings.txt");
7335
8658
  }
7336
- return path7.join(os4.homedir(), "AppData", "Roaming", "nvm", "settings.txt");
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() ? path7.join(process.env.LOCALAPPDATA.trim(), "nvm") : "",
7341
- process.env.AppData?.trim() ? path7.join(process.env.AppData.trim(), "nvm") : "",
7342
- path7.join(os4.homedir(), "AppData", "Local", "nvm"),
7343
- path7.join(os4.homedir(), "AppData", "Roaming", "nvm")
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 fs6.stat(candidateRoot);
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 fs6.readFile(resolveNvmWindowsSettingsPath(), "utf8");
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 fs6.readdir(root, { withFileTypes: true });
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}${path7.delimiter}${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 = path7.join(storageRoot, RUN_RESOURCE_INDEX_FILE_NAME);
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: path7.resolve(projectPath),
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 : isoNow3()
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 : isoNow3(),
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(path7.join(storageRoot, RUN_RESOURCE_INDEX_FILE_NAME), index);
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 = path7.resolve(input.projectPath);
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(path7.basename(normalizedProjectPath) || input.projectId) || input.projectId;
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: isoNow3()
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: isoNow3(),
8809
+ updated_at: isoNow4(),
7487
8810
  projects: nextProjects
7488
8811
  });
7489
- const resourceRoot = path7.join(storageRoot, resourceDir);
8812
+ const resourceRoot = path9.join(storageRoot, resourceDir);
7490
8813
  return {
7491
8814
  storageRoot,
7492
8815
  resourceRoot,
7493
- commandsPath: path7.join(resourceRoot, RUN_COMMANDS_FILE_NAME),
7494
- websitesPath: path7.join(resourceRoot, RUN_WEBSITES_FILE_NAME)
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 fs6.readFile(configPath, "utf8");
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 : isoNow3(),
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 fs6.readFile(configPath, "utf8");
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 : isoNow3(),
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 fs6.mkdir(path7.dirname(configPath), { recursive: true });
8851
+ await fs7.mkdir(path9.dirname(configPath), { recursive: true });
7529
8852
  const payload = {
7530
8853
  version: RUN_COMMANDS_SCHEMA_VERSION,
7531
- updated_at: isoNow3(),
8854
+ updated_at: isoNow4(),
7532
8855
  commands
7533
8856
  };
7534
- await fs6.writeFile(configPath, `${JSON.stringify(payload, null, 2)}
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 fs6.mkdir(path7.dirname(configPath), { recursive: true });
8862
+ await fs7.mkdir(path9.dirname(configPath), { recursive: true });
7540
8863
  const payload = {
7541
8864
  version: RUN_WEBSITES_SCHEMA_VERSION,
7542
- updated_at: isoNow3(),
8865
+ updated_at: isoNow4(),
7543
8866
  websites
7544
8867
  };
7545
- await fs6.writeFile(configPath, `${JSON.stringify(payload, null, 2)}
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 = isoNow3();
8907
+ const now = isoNow4();
7585
8908
  const sanitized = sanitizeRunCommandDraft(projectPath, draft);
7586
8909
  return {
7587
- id: `run-command-${randomUUID()}`,
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: isoNow3()
8936
+ updated_at: isoNow4()
7614
8937
  };
7615
8938
  };
7616
8939
  var createRunWebsite = (draft, source) => {
7617
- const now = isoNow3();
8940
+ const now = isoNow4();
7618
8941
  const sanitized = sanitizeRunWebsiteDraft(draft);
7619
8942
  return {
7620
- id: `run-website-${randomUUID()}`,
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: isoNow3()
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: isoNow3(),
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: isoNow3(),
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 = path7.join(settings.root, `v${nodeVersion}`);
9227
+ const nodeDirectory = path9.join(settings.root, `v${nodeVersion}`);
7905
9228
  try {
7906
- await fs6.access(path7.join(nodeDirectory, "node.exe"));
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: isoNow3()
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 = isoNow3();
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 = isoNow3();
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 = isoNow3();
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 = isoNow3();
8124
- const terminalId = `terminal-${randomUUID()}`;
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 = isoNow3();
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 fs7 from "fs/promises";
9579
+ import fs8 from "fs/promises";
8257
9580
  import net from "net";
8258
9581
  import os5 from "os";
8259
- import path8 from "path";
8260
- import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
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 = path8.join("state", "panda", "dev-manager");
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 isoNow4 = () => (/* @__PURE__ */ new Date()).toISOString();
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) => path8.join(codexHome, DEV_MANAGER_RELATIVE_ROOT);
8359
- var resolveConfigPath = (codexHome) => path8.join(resolveStateRoot(codexHome), CONFIG_FILE_NAME);
8360
- var resolveCredentialsPath = (codexHome) => path8.join(resolveStateRoot(codexHome), CREDENTIALS_FILE_NAME);
8361
- var resolveServicePidsPath = (codexHome) => path8.join(resolveStateRoot(codexHome), SERVICE_PIDS_FILE_NAME);
8362
- var resolveJobsDirectory = (codexHome) => path8.join(resolveStateRoot(codexHome), JOBS_DIRECTORY_NAME);
8363
- var resolveHelperDirectory = (codexHome) => path8.join(resolveStateRoot(codexHome), HELPERS_DIRECTORY_NAME);
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 ? path8.resolve(repoPath) : null,
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 fs7.readFile(filePath, "utf8");
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 fs7.mkdir(path8.dirname(filePath), { recursive: true });
8423
- await fs7.writeFile(filePath, `${JSON.stringify(payload, null, 2)}
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 ensureDirectory = async (targetPath) => {
8427
- await fs7.mkdir(targetPath, { recursive: true });
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 fs7.stat(repoPath).catch(() => null);
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) => path8.join(resolveJobsDirectory(codexHome), `${jobId}.json`);
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 fs7.readdir(jobsDirectory, { withFileTypes: true }).catch(() => []);
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(path8.join(jobsDirectory, entry.name)))
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-${randomUUID2()}`,
8464
- timestamp: isoNow4(),
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-${randomUUID2()}`,
9792
+ id: `dev-manager-job-${randomUUID3()}`,
8470
9793
  kind,
8471
9794
  title,
8472
9795
  status: "running",
8473
- created_at: isoNow4(),
8474
- started_at: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
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: isoNow4(),
8765
- updated_at: isoNow4()
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 = path8.join(
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 fs7.writeFile(filePath, content, "utf8");
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 = path8.join(repoPath, "release", "android", "panda-android-release.apk");
8887
- const latestJsonPath = path8.join(repoPath, "release", "android", "latest.json");
8888
- const apkStat = await fs7.stat(apkPath).catch(() => null);
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: path8.basename(apkPath),
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), '/t', '/f'], {
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: isoNow4(),
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: isoNow4()
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: isoNow4()
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: isoNow4(),
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: isoNow4()
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 ensureDirectory(resolveStateRoot(codexHome));
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 fs7.rm(userConfigPath, { force: true }).catch(() => void 0);
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 ensureDirectory(helperDirectory);
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 = path8.join(helperDirectory, "release-install-helper.cjs");
10027
- const helperPayloadPath = path8.join(helperDirectory, `${job.id}.json`);
10028
- await fs7.writeFile(helperSourcePath, createUpgradeHelperSource(), "utf8");
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: path8.join(config.repo_path, "release", "android", "panda-android-release.apk")
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 CODEX_VERSION_PROBE_TIMEOUT_MS = 2500;
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 = path9.join(
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 = path9.join(
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 = path9.join(
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 HUB_WEB_PUSH_VAPID_RELATIVE_PATH = path9.join(
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: "\u5FEB\u901F\u5BA1\u67E5\u5F53\u524D\u5DE5\u4F5C\u533A\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u9002\u5408\u5728\u63D0\u4EA4\u524D\u6216\u7EE7\u7EED\u7F16\u7801\u524D\u5148\u81EA\u68C0\u4E00\u904D\u3002"
11617
+ reason: "Review current workspace changes before continuing."
10281
11618
  };
10282
11619
  var FALLBACK_CODEX_COMMAND_CATALOG = [
10283
- { name: "compact", description: "\u538B\u7F29\u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u91CA\u653E\u7A97\u53E3\u5E76\u7EE7\u7EED\u5DE5\u4F5C\u3002", availability: "supported" },
10284
- { name: "copy", description: "\u590D\u5236\u6700\u8FD1\u4E00\u6B21\u52A9\u624B\u8F93\u51FA\u3002", availability: "unsupported" },
10285
- { name: "diff", description: "\u67E5\u770B\u5F53\u524D\u5DE5\u4F5C\u533A\u6700\u8FD1\u53D8\u66F4\u3002", availability: "unsupported" },
10286
- { name: "feedback", description: "\u63D0\u4EA4\u5173\u4E8E Codex \u7684\u53CD\u9988\u3002", availability: "unsupported" },
10287
- { name: "fork", description: "\u4ECE\u5F53\u524D\u4F1A\u8BDD\u6D3E\u751F\u4E00\u4E2A\u65B0\u7684\u5206\u652F\u4F1A\u8BDD\u3002", availability: "unsupported" },
10288
- { name: "init", description: "\u521D\u59CB\u5316 Codex \u9879\u76EE\u6587\u4EF6\u3002", availability: "unsupported" },
10289
- { name: "mcp", description: "\u67E5\u770B\u5F53\u524D\u53EF\u7528\u7684 MCP \u670D\u52A1\u548C\u5DE5\u5177\u3002", availability: "supported" },
10290
- { name: "model", description: "\u5207\u6362\u540E\u7EED\u5BF9\u8BDD\u4F7F\u7528\u7684\u6A21\u578B\u3002", availability: "supported" },
10291
- { name: "new", description: "\u5F00\u59CB\u4E00\u4E2A\u65B0\u4F1A\u8BDD\u3002", availability: "unsupported" },
10292
- { name: "permissions", description: "\u67E5\u770B\u6216\u8C03\u6574\u786E\u8BA4\u7B56\u7565\u3002", availability: "unsupported" },
10293
- { name: "personality", description: "\u5207\u6362\u54CD\u5E94\u4EBA\u683C\u3002", availability: "unsupported" },
10294
- { name: "rename", description: "\u91CD\u547D\u540D\u5F53\u524D\u4F1A\u8BDD\u3002", availability: "supported" },
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: "\u5207\u6362\u771F\u5B9E Codex CLI \u6A21\u578B\uFF0C\u662F\u6700\u5E38\u7528\u4E5F\u6700\u76F4\u63A5\u5F71\u54CD\u5BF9\u8BDD\u4F53\u9A8C\u7684\u547D\u4EE4\u3002"
11646
+ reason: "Switch the active Codex CLI model."
10314
11647
  },
10315
11648
  {
10316
11649
  name: "status",
10317
- reason: "\u5FEB\u901F\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\u3001\u4E0A\u4E0B\u6587\u5360\u7528\u548C\u6C99\u7BB1\u7B49\u5173\u952E\u4FE1\u606F\uFF0C\u9002\u5408\u5148\u81EA\u68C0\u3002"
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: "\u628A\u9879\u76EE\u91CC\u53EF\u7528\u6280\u80FD\u76F4\u63A5\u66B4\u9732\u51FA\u6765\uFF0C\u80FD\u5E2E\u52A9\u7528\u6237\u53D1\u73B0 Panda \u5DF2\u63A5\u5165\u7684\u80FD\u529B\u3002"
11655
+ reason: "Show available project skills."
10323
11656
  },
10324
11657
  {
10325
11658
  name: "mcp",
10326
- reason: "\u68C0\u67E5\u5916\u90E8\u5DE5\u5177\u548C\u670D\u52A1\u63A5\u5165\u72B6\u6001\uFF0C\u51FA\u4E86\u95EE\u9898\u65F6\u6392\u67E5\u6548\u7387\u6700\u9AD8\u3002"
11659
+ reason: "Inspect MCP tools and services."
10327
11660
  },
10328
11661
  {
10329
11662
  name: "rename",
10330
- reason: "\u6574\u7406\u4F1A\u8BDD\u6807\u9898\u6210\u672C\u4F4E\u3001\u9891\u7387\u9AD8\uFF0C\u9002\u5408\u653E\u8FDB\u9996\u5C4F\u547D\u4EE4\u5217\u8868\u3002"
11663
+ reason: "Rename the current conversation."
10331
11664
  },
10332
11665
  {
10333
11666
  name: "compact",
10334
- reason: "\u4E0A\u4E0B\u6587\u53D8\u957F\u65F6\u5F88\u5B9E\u7528\uFF0C\u4F46\u53C8\u4E0D\u50CF\u5B9E\u9A8C\u6027\u547D\u4EE4\u90A3\u6837\u5BB9\u6613\u8BA9\u4EBA\u56F0\u60D1\u3002"
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: path9.join(
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: path9.join(
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: path9.join(
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 isoNow5 = () => (/* @__PURE__ */ new Date()).toISOString();
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: isoNow5(),
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: isoNow5(),
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 fs8.stat(targetPath)).isFile();
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 = path9.posix.normalize(pathname);
11844
+ const normalizedPathname = path11.posix.normalize(pathname);
10507
11845
  if (normalizedPathname.startsWith("/..")) {
10508
11846
  return null;
10509
11847
  }
10510
- const webUiRootPath = path9.resolve(webUiRoot);
11848
+ const webUiRootPath = path11.resolve(webUiRoot);
10511
11849
  const relativePath = normalizedPathname === "/" ? "index.html" : normalizedPathname.replace(/^\/+/, "");
10512
- const candidatePath = path9.resolve(webUiRootPath, relativePath.split("/").join(path9.sep));
10513
- const candidateRelativePath = path9.relative(webUiRootPath, candidatePath);
10514
- if (candidateRelativePath.startsWith("..") || path9.isAbsolute(candidateRelativePath)) {
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 = path9.extname(relativePath) !== "";
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[path9.extname(candidatePath).toLowerCase()] ?? "application/octet-stream"
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 = path9.join(webUiRootPath, "index.html");
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 = path9.resolve(projectPath);
10556
- const resolvedCandidatePath = path9.resolve(projectPath, candidatePath);
10557
- const relativePath = path9.relative(resolvedProjectPath, resolvedCandidatePath);
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("..") && !path9.isAbsolute(relativePath);
11899
+ return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
10562
11900
  };
10563
11901
  const isAbsolutePathInsideProject = (projectPath, candidatePath) => {
10564
- const resolvedProjectPath = path9.resolve(projectPath);
10565
- const resolvedCandidatePath = path9.resolve(candidatePath);
10566
- const relativePath = path9.relative(resolvedProjectPath, resolvedCandidatePath);
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("..") && !path9.isAbsolute(relativePath);
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 ? path9.resolve(projectPath, normalizedPath) : path9.resolve(projectPath);
11914
+ const absolutePath = normalizedPath ? path11.resolve(projectPath, normalizedPath) : path11.resolve(projectPath);
10577
11915
  if (!isAbsolutePathInsideProject(projectPath, absolutePath)) {
10578
- throw new Error("\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5F53\u524D\u9879\u76EE\u5185\u3002");
11916
+ throw new Error("Target path is outside the current project.");
10579
11917
  }
10580
11918
  let realPath;
10581
11919
  try {
10582
- realPath = await fs8.realpath(absolutePath);
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("\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5F53\u524D\u9879\u76EE\u5185\u3002");
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 = path9.extname(value).trim().toLowerCase();
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 fs8.open(filePath, "r");
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 fs8.readdir(directoryPath);
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 = path9.join(parentRealPath, entry.name);
12006
+ const entryPath = path11.join(parentRealPath, entry.name);
10669
12007
  if (entry.isSymbolicLink()) {
10670
12008
  try {
10671
- const realPath = await fs8.realpath(entryPath);
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 fs8.stat(entryPath).catch(() => null);
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 = path9.join(projectPath, filePath);
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 = path9.join(projectPath, filePath);
12327
+ const absolutePath = path11.join(projectPath, filePath);
10990
12328
  try {
10991
- const content = await fs8.readFile(absolutePath, "utf8");
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: isoNow5()
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: isoNow5()
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 detectCodexCliVersion = async () => new Promise((resolve) => {
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
- }, CODEX_VERSION_PROBE_TIMEOUT_MS);
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() || path9.join(os6.homedir(), ".panda");
11287
- await fs8.mkdir(baseDirectory, { recursive: true });
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 = path9.join(baseDirectory, "cache");
11298
- await fs8.mkdir(cacheDirectory, { recursive: true });
11299
- return path9.join(cacheDirectory, "codex-command-catalog.json");
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 fs8.readFile(cacheFilePath, "utf8");
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 fs8.writeFile(cacheFilePath, `${JSON.stringify(catalog, null, 2)}
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 = path9.join(baseDirectory, "config");
11323
- await fs8.mkdir(configDirectory, { recursive: true });
11324
- return path9.join(configDirectory, "codex-visible-commands.json");
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 fs8.writeFile(configFilePath, `${JSON.stringify(config, null, 2)}
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 fs8.readFile(configFilePath, "utf8");
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 \u6CA1\u6709\u8FD4\u56DE\u547D\u4EE4\u8349\u7A3F\u3002");
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 \u8FD4\u56DE\u7684\u547D\u4EE4\u8349\u7A3F\u4E0D\u662F\u6709\u6548 JSON\u3002");
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() || path9.join(os6.homedir(), ".codex");
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 = path9.join(resolveCodexHomePath(codexHome2), options.relativePath);
11620
- let writeQueue = fs8.mkdir(path9.dirname(filePath), { recursive: true }).then(
11621
- () => fs8.writeFile(
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(() => fs8.appendFile(filePath, formatDiagnosticLogEntry(level, message, payload), "utf8")).catch(() => void 0);
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 path9.basename(normalized) || normalized;
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()}\u2026`;
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(/^["'`\[\]【】()()\s]+|["'`\[\]【】()()\s]+$/g, "").replace(/^(标题|Title)\s*[::-]\s*/i, "").replace(/\s+/g, " ").trim();
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 ? `\u7528\u6237\u9996\u6761\u6D88\u606F\uFF1A
13014
+ message ? `First user message:
11677
13015
  ${message}` : null,
11678
- attachmentNames.length > 0 ? `\u9644\u4EF6\uFF1A
11679
- ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
13016
+ attachmentNames.length > 0 ? `Attachments:
13017
+ ` + attachmentNames.map((name) => `- ${name}`).join("\n") : null
11680
13018
  ].filter(Boolean);
11681
13019
  return [
11682
- "\u8BF7\u6839\u636E\u4E0B\u9762\u7684\u65B0\u4F1A\u8BDD\u9996\u6761\u6D88\u606F\u751F\u6210\u4E00\u4E2A\u7B80\u77ED\u7684\u4E2D\u6587\u4F1A\u8BDD\u6807\u9898\u3002",
11683
- "\u8981\u6C42\uFF1A",
11684
- "1. \u53EA\u8F93\u51FA\u6807\u9898\u672C\u8EAB\uFF0C\u4E0D\u8981\u89E3\u91CA\uFF0C\u4E0D\u8981\u52A0\u5F15\u53F7\u3002",
11685
- "2. \u6807\u9898\u8981\u51C6\u786E\u6982\u62EC\u4EFB\u52A1\u76EE\u6807\uFF0C\u4E0D\u8981\u7167\u6284\u539F\u53E5\u3002",
11686
- "3. \u5C3D\u91CF\u63A7\u5236\u5728 4 \u5230 14 \u4E2A\u6C49\u5B57\uFF0C\u6216\u7B49\u4EF7\u7684\u77ED\u82F1\u6587\u957F\u5EA6\u3002",
11687
- "4. \u907F\u514D\u7A7A\u6CDB\u8BCD\uFF0C\u6BD4\u5982\u201C\u6C42\u52A9\u201D\u201C\u95EE\u9898\u201D\u201C\u804A\u5929\u201D\u201C\u65B0\u4F1A\u8BDD\u201D\u3002",
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-OBU4SE67.mjs");
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: isoNow5(),
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 = findLastUserTimelineEntryIndex(entries);
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: lastUserIndex > 0,
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()}\u2026`;
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} \u4E2A\u6587\u4EF6`;
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 || "\u5DE5\u5177\u6267\u884C");
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: isoNow5(),
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: isoNow5(),
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 || "\u5F53\u524D\u4F1A\u8BDD";
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 "\u70B9\u5F00\u67E5\u770B\u672C\u6B21\u4F1A\u8BDD\u7684\u6700\u7EC8\u56DE\u590D\u3002";
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()}\u2026`;
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 \u4F1A\u8BDD\u5DF2\u5B8C\u6210",
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: isoNow5() };
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: "\u6B64\u53D8\u66F4\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u8865\u4E01\u5185\u5BB9"
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 buildWorkspaceProjectStats = (projects, sessions, selectedSessionId) => projects.map((project) => {
12726
- const projectSessions = sessions.filter((session) => session.project_id === project.id);
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: 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: isoNow5(),
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 (projectId && session.project_id !== projectId) {
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: isoNow5(),
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: isoNow5(),
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: isoNow5(),
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 ?? isoNow5();
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 detectCodexCliVersion();
14420
+ const cliVersion = await detectCodexCliVersion2();
13039
14421
  const catalog = codexCommandCatalogSchema.parse({
13040
14422
  cli_version: cliVersion,
13041
- loaded_at: isoNow5(),
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.project.path,
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 nextTitle = sanitizeGeneratedSessionTitle(generated);
13105
- if (!nextTitle || nextTitle === input.fallbackTitle) {
13106
- return;
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
- const currentSession = await findSnapshotSession(input.sessionId);
13109
- if (!currentSession || currentSession.title !== input.fallbackTitle) {
13110
- return;
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
- await renameSession(input.sessionId, nextTitle);
13113
- } catch (error) {
13114
- diagnosticLogger.warn({
14708
+ broadcastEvent("session.updated", {
13115
14709
  sessionId: input.sessionId,
13116
- projectId: input.project.id,
13117
- projectPath: input.project.path,
13118
- model: input.model,
13119
- error: error instanceof Error ? error.message : "Unknown error"
13120
- }, "Failed to generate background session title.");
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: "\u65E0\u6CD5\u8BC6\u522B\u547D\u4EE4",
14775
+ title: "Unknown command",
13131
14776
  status: "failed",
13132
- body: "\u53EA\u6709\u4EE5 / \u5F00\u5934\u7684\u9996\u4E2A\u8BCD\u4F1A\u88AB\u8BC6\u522B\u4E3A\u547D\u4EE4\u3002"
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: `\u672A\u8BC6\u522B /${parsed.name}`,
14787
+ title: `Unknown command /${parsed.name}`,
13143
14788
  status: "failed",
13144
- body: "Panda \u8FD8\u6CA1\u6709\u8BC6\u522B\u5230\u8FD9\u4E2A Codex \u547D\u4EE4\u3002"
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} \u6682\u672A\u63A5\u5165`,
14797
+ title: `/${command.name} is not implemented`,
13153
14798
  description: command.description,
13154
14799
  status: "completed",
13155
- body: "\u547D\u4EE4\u76EE\u5F55\u5DF2\u7ECF\u63A5\u5165\uFF0C\u4F46\u8FD9\u4E2A\u547D\u4EE4\u7684\u6267\u884C\u6D41\u8FD8\u6CA1\u6709\u5728 Panda \u4E2D\u5B9E\u73B0\u3002"
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: "\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001",
14810
+ title: "Current session status",
13166
14811
  description: command.description,
13167
14812
  status: "completed",
13168
14813
  body: [
13169
- `\u4F1A\u8BDD\uFF1A${session.title}`,
13170
- `\u8FD0\u884C\u72B6\u6001\uFF1A${session.run_state === "running" ? "\u8FD0\u884C\u4E2D" : session.run_state === "completed" ? "\u5DF2\u5B8C\u6210" : "\u7A7A\u95F2"}`,
13171
- `\u6A21\u578B\uFF1A${config.model ?? "\u672A\u8BBE\u7F6E"}`,
13172
- `\u63A8\u7406\u5F3A\u5EA6\uFF1A${config.reasoningEffort ?? "\u9ED8\u8BA4"}`,
13173
- `\u786E\u8BA4\u7B56\u7565\uFF1A${config.approvalPolicy ?? "\u9ED8\u8BA4"}`,
13174
- `\u6C99\u7BB1\u6A21\u5F0F\uFF1A${config.sandboxMode ?? "\u9ED8\u8BA4"}`,
13175
- usage ? `\u4E0A\u4E0B\u6587\u7A97\u53E3\uFF1A${usage.used_tokens}/${usage.total_tokens}\uFF08${usage.percent_used.toFixed(1)}%\uFF09` : "\u4E0A\u4E0B\u6587\u7A97\u53E3\uFF1A\u6682\u65E0\u6570\u636E"
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: `\u6280\u80FD\u5217\u8868 (${skills.length})`,
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") : "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u6280\u80FD\u3002"
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 \u670D\u52A1 (${servers.length})`,
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(" \xB7 ")
13207
- ).join("\n") : "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684 MCP \u670D\u52A1\u3002"
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: "\u5DF2\u63D0\u4EA4\u4E0A\u4E0B\u6587\u538B\u7F29",
14861
+ title: "Context compaction requested",
13217
14862
  description: command.description,
13218
14863
  status: "completed",
13219
- body: "\u538B\u7F29\u8BF7\u6C42\u5DF2\u7ECF\u63D0\u4EA4\u7ED9 Codex\uFF0C\u540E\u7EED\u7ED3\u679C\u4F1A\u7EE7\u7EED\u4F53\u73B0\u5728\u5F53\u524D\u4F1A\u8BDD\u91CC\u3002"
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: "\u4F1A\u8BDD\u5DF2\u91CD\u547D\u540D",
14874
+ title: "Session renamed",
13230
14875
  description: command.description,
13231
14876
  status: "completed",
13232
- body: `\u5F53\u524D\u4F1A\u8BDD\u5DF2\u91CD\u547D\u540D\u4E3A \u201C${parsed.args}\u201D\u3002`
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: "\u91CD\u547D\u540D\u5F53\u524D\u4F1A\u8BDD",
14884
+ title: "Rename current session",
13240
14885
  description: command.description,
13241
14886
  status: "awaiting_input",
13242
- body: "\u8F93\u5165\u65B0\u7684\u4F1A\u8BDD\u540D\u79F0\uFF0C\u7136\u540E\u5728\u8FD9\u4E2A\u9762\u677F\u91CC\u76F4\u63A5\u63D0\u4EA4\u3002",
14887
+ body: "Enter a new session name, then submit it in this panel.",
13243
14888
  inputType: "text",
13244
- inputPlaceholder: "\u8F93\u5165\u65B0\u7684\u4F1A\u8BDD\u540D\u79F0",
13245
- submitLabel: "\u91CD\u547D\u540D"
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: "\u9009\u62E9\u6A21\u578B",
13259
- description: "\u9009\u62E9\u540E\u4F1A\u66F4\u65B0 Panda \u5F53\u524D\u4F1A\u8BDD\u540E\u7EED\u53D1\u9001\u65F6\u4F7F\u7528\u7684\u6A21\u578B\u3002",
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 ? "\u7EE7\u7EED\u5728\u5217\u8868\u91CC\u9009\u62E9\u4E00\u4E2A\u6A21\u578B\uFF0C\u9762\u677F\u4F1A\u76F4\u63A5\u66F4\u65B0\u7ED3\u679C\u3002" : "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u6A21\u578B\u3002",
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: `/${command.name} \u5DF2\u8BC6\u522B`,
14923
+ title: "/" + command.name + " recognized",
13279
14924
  description: command.description,
13280
14925
  status: "completed",
13281
- body: "\u547D\u4EE4\u76EE\u5F55\u5DF2\u7ECF\u63A5\u5165\uFF0C\u4F46\u8FD9\u4E2A\u547D\u4EE4\u7684\u6267\u884C\u6D41\u8FD8\u6CA1\u6709\u5728 Panda \u4E2D\u5B9E\u73B0\u3002"
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("\u547D\u4EE4\u9762\u677F\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u8BE5\u547D\u4EE4\u3002");
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("\u8BF7\u8F93\u5165\u65B0\u7684\u4F1A\u8BDD\u540D\u79F0\u3002");
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: `\u5F53\u524D\u4F1A\u8BDD\u5DF2\u91CD\u547D\u540D\u4E3A \u201C${nextName}\u201D\u3002`,
13300
- updated_at: isoNow5(),
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("\u8BF7\u9009\u62E9\u4E00\u4E2A\u6A21\u578B\u3002");
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("\u6240\u9009\u6A21\u578B\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00 /model\u3002");
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
- `\u540E\u7EED\u6D88\u606F\u5C06\u4F18\u5148\u4F7F\u7528 ${selectedModel.label}\u3002`,
13322
- selectedModel.defaultReasoningEffort ? `\u9ED8\u8BA4\u63A8\u7406\u5F3A\u5EA6\uFF1A${selectedModel.defaultReasoningEffort}` : null
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: isoNow5(),
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: "\u8FD9\u4E2A\u547D\u4EE4\u9762\u677F\u4E0D\u63A5\u53D7\u540E\u7EED\u8F93\u5165\u3002",
13339
- updated_at: isoNow5(),
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-OBU4SE67.mjs");
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: isoNow5(),
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 fs8.stat(resolved.realPath).catch((error) => {
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("\u76EE\u6807\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\u3002");
15224
+ throw new Error("Target path is not a directory.");
13548
15225
  }
13549
- const entries = await fs8.readdir(resolved.realPath, {
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: isoNow5()
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 fs8.stat(resolved.realPath).catch((error) => {
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("\u76EE\u6807\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6\u3002");
15262
+ throw new Error("Target path is not a file.");
13586
15263
  }
13587
- const fileName = path9.basename(resolved.normalizedPath || resolved.realPath);
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: isoNow5()
15290
+ loaded_at: isoNow6()
13614
15291
  };
13615
15292
  }
13616
- const buffer2 = await fs8.readFile(resolved.realPath);
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: isoNow5()
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: isoNow5()
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: isoNow5()
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("\u7F3A\u5C11\u76EE\u6807\u6587\u4EF6\u8DEF\u5F84\u3002");
15355
+ throw new Error("Missing target file path.");
13679
15356
  }
13680
15357
  if (!isPathInsideProject(projectPath, nextPath)) {
13681
- throw new Error("\u76EE\u6807\u6587\u4EF6\u4E0D\u5728\u5F53\u524D\u5DE5\u4F5C\u533A\u4E2D\u3002");
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 ? `${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("\u8FD9\u8F6E\u6539\u52A8\u5305\u542B\u8D85\u51FA\u5F53\u524D\u5DE5\u4F5C\u533A\u7684\u8DEF\u5F84\uFF0C\u65E0\u6CD5\u5B89\u5168\u56DE\u6EDA\u3002");
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("\u5F53\u524D\u8FD9\u8F6E\u6539\u52A8\u5C1A\u672A\u5B8C\u6210\uFF0C\u6682\u65F6\u4E0D\u80FD\u56DE\u6EDA\u3002");
15402
+ throw new Error("This change set is not completed yet.");
13727
15403
  }
13728
15404
  if (changeSet.files.length === 0) {
13729
- throw new Error("\u8FD9\u8F6E\u5BF9\u8BDD\u6CA1\u6709\u53EF\u56DE\u6EDA\u7684\u6587\u4EF6\u6539\u52A8\u3002");
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("\u8FD9\u8F6E\u6539\u52A8\u7F3A\u5C11\u53EF\u9006\u8865\u4E01\uFF0C\u6682\u65F6\u65E0\u6CD5\u56DE\u6EDA\u3002");
15410
+ throw new Error("This change set does not contain a reversible patch.");
13735
15411
  }
13736
- const tempDir = await fs8.mkdtemp(path9.join(os6.tmpdir(), "panda-change-set-rollback-"));
13737
- const patchFilePath = path9.join(tempDir, "rollback.patch");
13738
- await fs8.writeFile(patchFilePath, patchText, "utf8");
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 \u65E0\u6CD5\u53CD\u5411\u5E94\u7528\u8FD9\u8F6E\u6539\u52A8\u7684\u8865\u4E01\u3002";
13745
- throw new Error(`\u65E0\u6CD5\u5B89\u5168\u56DE\u6EDA\u8FD9\u8F6E\u6539\u52A8\uFF1A${reason}`);
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 fs8.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
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("\u8BF7\u8F93\u5165\u63D0\u4EA4\u4FE1\u606F\u3002");
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("\u8BF7\u9009\u62E9\u8981\u5207\u6362\u7684\u5206\u652F\u3002");
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) => `${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 fs8.stat(candidate);
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 fs8.stat(targetPath);
15542
+ const stat = await fs9.stat(targetPath);
13867
15543
  if (!stat.isDirectory()) {
13868
15544
  return null;
13869
15545
  }
13870
- const entries = await fs8.readdir(targetPath, { withFileTypes: true });
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 fs8.readdir(targetPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path9.join(targetPath, entry.name));
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 = path9.resolve(parentPath);
13898
- const parentStat = await fs8.stat(resolvedParentPath).catch(() => null);
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 = path9.join(resolvedParentPath, directoryName);
15579
+ const targetPath = path11.join(resolvedParentPath, directoryName);
13904
15580
  try {
13905
- await fs8.mkdir(targetPath);
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
- can_show_terminal: true
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: isoNow5()
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
- `attachment; filename="${download.artifact.file_name}"`
15828
+ 'attachment; filename="' + download.artifact.file_name + '"'
14152
15829
  );
14153
15830
  reply.type("application/vnd.android.package-archive");
14154
- return reply.send(await fs8.readFile(download.filePath));
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 ?? isoNow5(),
14218
- received_at: registered.agent.last_seen_at ?? isoNow5()
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 ?? isoNow5(),
14244
- received_at: registered.agent.last_seen_at ?? isoNow5()
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 \u6D4B\u8BD5\u63A8\u9001",
14339
- body: "\u8FD9\u662F\u4E00\u6761\u6765\u81EA Hub \u7684\u6D4B\u8BD5\u901A\u77E5\uFF0C\u8BF4\u660E\u540E\u53F0 Web Push \u94FE\u8DEF\u5DF2\u7ECF\u6253\u901A\u3002",
16015
+ title: "Panda test push",
16016
+ body: "This is a test notification from Hub.",
14340
16017
  url: "/",
14341
- tag: `panda-web-push-test:${Date.now()}`
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("\u76EE\u6807\u6587\u4EF6\u5F53\u524D\u6CA1\u6709\u5F85\u5904\u7406\u7684\u6539\u52A8\u3002");
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("\u8BF7\u9009\u62E9\u8981\u5207\u6362\u7684\u5206\u652F\u3002");
16386
+ throw new Error("Please select a branch.");
14710
16387
  }
14711
16388
  if (workspace.branch === targetBranch) {
14712
- throw new Error("\u5F53\u524D\u5DF2\u7ECF\u5728\u8BE5\u5206\u652F\u3002");
16389
+ throw new Error("Already on this branch.");
14713
16390
  }
14714
16391
  if (workspace.files.length > 0) {
14715
- throw new Error("\u5207\u6362\u5206\u652F\u524D\u8BF7\u5148\u63D0\u4EA4\u6216\u64A4\u56DE\u5F53\u524D\u5206\u652F\u6539\u52A8\u3002");
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("\u76EE\u6807\u5206\u652F\u4E0D\u5B58\u5728\u3002");
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 \u6CA1\u6709\u751F\u6210\u4EFB\u4F55\u53EF\u4FDD\u5B58\u7684\u9879\u76EE\u547D\u4EE4\u3002");
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 \u6CA1\u6709\u751F\u6210\u4EFB\u4F55\u53EF\u4FDD\u5B58\u7684\u7F51\u9875\u5730\u5740\u3002");
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("\u8BF7\u9009\u62E9\u8981\u8FD0\u884C\u7684\u547D\u4EE4\u3002");
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("\u76EE\u6807\u547D\u4EE4\u4E0D\u5B58\u5728\u3002");
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("\u8BE5\u547D\u4EE4\u6CA1\u6709\u53EF\u6267\u884C\u7684\u505C\u6B62\u547D\u4EE4\u3002");
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 ? `${command.name} \xB7 \u505C\u6B62` : command.name,
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("\u7F3A\u5C11\u76EE\u6807\u7EC8\u7AEF\u3002");
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: `project-${slugify(name)}-${Date.now()}`,
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 = isoNow5();
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
- id: sessionId,
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
- mode: "managed",
15353
- health: "active",
15354
- branch: project.branch,
15355
- worktree: project.worktree,
15356
- summary: input ? truncateSummary(input) : attachments[0]?.name ?? "",
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 = isoNow5();
15831
- const summary = input ? truncateSummary(input) : attachments[0]?.name ?? "\u9644\u4EF6";
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: `${USER_OVERLAY_ENTRY_PREFIX}${sessionId}:${Date.now()}`,
17905
+ id: USER_OVERLAY_ENTRY_PREFIX + sessionId + ":" + Date.now(),
15857
17906
  kind: "user",
15858
- title: "\u4F60",
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 = isoNow5();
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: `entry-system-interrupt-${Date.now()}`,
17989
+ id: "entry-system-interrupt-" + Date.now(),
15941
17990
  kind: "system",
15942
- title: "\u4E2D\u65AD\u8BF7\u6C42",
15943
- body: "\u5DF2\u63D0\u4EA4\u4E2D\u65AD\u8BF7\u6C42\u3002",
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 = isoNow5();
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: `entry-system-interrupt-failed-${Date.now()}`,
18062
+ id: "entry-system-interrupt-failed-" + Date.now(),
16014
18063
  kind: "system",
16015
- title: "\u4E2D\u65AD\u5931\u8D25",
16016
- body: `\u4E2D\u65AD\u672A\u5B8C\u6210\uFF1A${failureMessage}`,
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 fs8.readFile(asset.filePath));
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,