@jamiexiongr/panda-hub 0.1.23 → 0.1.25

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.
@@ -2273,6 +2273,58 @@ var sessionGitActionResponseSchema = external_exports.object({
2273
2273
  ok: external_exports.literal(true),
2274
2274
  workspace: sessionGitWorkspaceSchema
2275
2275
  });
2276
+ var sessionTurnActionSchema = external_exports.enum([
2277
+ "rollback"
2278
+ ]);
2279
+ var sessionTurnActionResponseSchema = external_exports.object({
2280
+ ok: external_exports.literal(true),
2281
+ turn_id: external_exports.string(),
2282
+ change_set_id: external_exports.string(),
2283
+ workspace: sessionGitWorkspaceSchema
2284
+ });
2285
+ var sessionFilePreviewNodeKindSchema = external_exports.enum([
2286
+ "directory",
2287
+ "file"
2288
+ ]);
2289
+ var sessionFilePreviewFileKindSchema = external_exports.enum([
2290
+ "markdown",
2291
+ "code",
2292
+ "text",
2293
+ "image",
2294
+ "binary"
2295
+ ]);
2296
+ var sessionFilePreviewTreeNodeSchema = external_exports.object({
2297
+ path: external_exports.string(),
2298
+ name: external_exports.string(),
2299
+ kind: sessionFilePreviewNodeKindSchema,
2300
+ has_children: external_exports.boolean(),
2301
+ extension: external_exports.string().nullable().default(null),
2302
+ file_kind: sessionFilePreviewFileKindSchema.nullable().default(null),
2303
+ size_bytes: external_exports.number().int().nonnegative().nullable().default(null)
2304
+ });
2305
+ var sessionFilePreviewTreeResponseSchema = external_exports.object({
2306
+ session_id: external_exports.string(),
2307
+ project_id: external_exports.string(),
2308
+ root_path: external_exports.string(),
2309
+ parent_path: external_exports.string().nullable().default(null),
2310
+ nodes: external_exports.array(sessionFilePreviewTreeNodeSchema),
2311
+ loaded_at: external_exports.string()
2312
+ });
2313
+ var sessionFilePreviewContentResponseSchema = external_exports.object({
2314
+ session_id: external_exports.string(),
2315
+ project_id: external_exports.string(),
2316
+ path: external_exports.string(),
2317
+ name: external_exports.string(),
2318
+ extension: external_exports.string().nullable().default(null),
2319
+ file_kind: sessionFilePreviewFileKindSchema,
2320
+ mime_type: external_exports.string().nullable().default(null),
2321
+ size_bytes: external_exports.number().int().nonnegative().nullable().default(null),
2322
+ encoding: external_exports.enum(["utf8", "base64"]).nullable().default(null),
2323
+ is_truncated: external_exports.boolean(),
2324
+ content_text: external_exports.string().nullable().default(null),
2325
+ content_base64: external_exports.string().nullable().default(null),
2326
+ loaded_at: external_exports.string()
2327
+ });
2276
2328
  var sessionRunCommandShellSchema = external_exports.enum([
2277
2329
  "auto",
2278
2330
  "powershell",
@@ -10071,6 +10123,72 @@ var USER_OVERLAY_ENTRY_PREFIX = "overlay-user:";
10071
10123
  var USER_ENTRY_MATCH_EARLY_SKEW_MS = 5e3;
10072
10124
  var TIMELINE_TOOL_SUMMARY_LIMIT = 160;
10073
10125
  var HTTP_COMPRESSION_MIN_BYTES = 4 * 1024;
10126
+ var DEFAULT_SESSION_TITLE_GENERATION_MODEL = "gpt-5.4-mini";
10127
+ var SESSION_TITLE_GENERATION_TIMEOUT_MS = 25e3;
10128
+ var SESSION_GENERATED_TITLE_MAX_LENGTH = 30;
10129
+ var SESSION_FILE_PREVIEW_ROOT_PATH = "/";
10130
+ var SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT = 256 * 1024;
10131
+ var SESSION_FILE_PREVIEW_IMAGE_BYTE_LIMIT = 6 * 1024 * 1024;
10132
+ var SESSION_FILE_PREVIEW_MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown"]);
10133
+ var SESSION_FILE_PREVIEW_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
10134
+ ".ts",
10135
+ ".tsx",
10136
+ ".js",
10137
+ ".jsx",
10138
+ ".json",
10139
+ ".css",
10140
+ ".html",
10141
+ ".xml",
10142
+ ".yml",
10143
+ ".yaml",
10144
+ ".sh",
10145
+ ".java"
10146
+ ]);
10147
+ var SESSION_FILE_PREVIEW_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
10148
+ ".txt",
10149
+ ".log"
10150
+ ]);
10151
+ var SESSION_FILE_PREVIEW_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
10152
+ ".png",
10153
+ ".jpg",
10154
+ ".jpeg",
10155
+ ".webp",
10156
+ ".gif",
10157
+ ".svg"
10158
+ ]);
10159
+ var SESSION_FILE_PREVIEW_CODE_FILENAMES = /* @__PURE__ */ new Set([
10160
+ "dockerfile",
10161
+ "makefile",
10162
+ "jenkinsfile"
10163
+ ]);
10164
+ var SESSION_FILE_PREVIEW_CODE_NAME_PATTERNS = [
10165
+ /^\.(?:env(?:\..+)?)$/i,
10166
+ /^\.(?:gitignore|gitattributes|npmrc|yarnrc|editorconfig)$/i,
10167
+ /^\.(?:prettierrc|eslintrc)(?:\..+)?$/i
10168
+ ];
10169
+ var SESSION_FILE_PREVIEW_MIME_TYPES = {
10170
+ ".css": "text/css; charset=utf-8",
10171
+ ".gif": "image/gif",
10172
+ ".html": "text/html; charset=utf-8",
10173
+ ".java": "text/x-java-source; charset=utf-8",
10174
+ ".jpeg": "image/jpeg",
10175
+ ".jpg": "image/jpeg",
10176
+ ".js": "text/javascript; charset=utf-8",
10177
+ ".json": "application/json; charset=utf-8",
10178
+ ".log": "text/plain; charset=utf-8",
10179
+ ".markdown": "text/markdown; charset=utf-8",
10180
+ ".md": "text/markdown; charset=utf-8",
10181
+ ".png": "image/png",
10182
+ ".sh": "text/x-shellscript; charset=utf-8",
10183
+ ".svg": "image/svg+xml",
10184
+ ".ts": "text/typescript; charset=utf-8",
10185
+ ".tsx": "text/typescript; charset=utf-8",
10186
+ ".txt": "text/plain; charset=utf-8",
10187
+ ".webp": "image/webp",
10188
+ ".xml": "application/xml; charset=utf-8",
10189
+ ".yaml": "text/yaml; charset=utf-8",
10190
+ ".yml": "text/yaml; charset=utf-8"
10191
+ };
10074
10192
  var WEB_UI_CONTENT_TYPES = {
10075
10193
  ".css": "text/css; charset=utf-8",
10076
10194
  ".html": "text/html; charset=utf-8",
@@ -10090,6 +10208,11 @@ Plan mode is enabled for this message only. Before doing substantial work, creat
10090
10208
  </user_instructions>
10091
10209
 
10092
10210
  `;
10211
+ var LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION = 2;
10212
+ var REVIEW_VISIBLE_CODEX_COMMAND_ENTRY = {
10213
+ name: "review",
10214
+ 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"
10215
+ };
10093
10216
  var FALLBACK_CODEX_COMMAND_CATALOG = [
10094
10217
  { 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" },
10095
10218
  { name: "copy", description: "\u590D\u5236\u6700\u8FD1\u4E00\u6B21\u52A9\u624B\u8F93\u51FA\u3002", availability: "unsupported" },
@@ -10117,7 +10240,7 @@ var PANDA_SUPPORTED_COMMANDS = /* @__PURE__ */ new Set([
10117
10240
  "status"
10118
10241
  ]);
10119
10242
  var DEFAULT_VISIBLE_CODEX_COMMAND_CONFIG = {
10120
- version: 1,
10243
+ version: LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION,
10121
10244
  visible_commands: [
10122
10245
  {
10123
10246
  name: "model",
@@ -10127,6 +10250,7 @@ var DEFAULT_VISIBLE_CODEX_COMMAND_CONFIG = {
10127
10250
  name: "status",
10128
10251
  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"
10129
10252
  },
10253
+ REVIEW_VISIBLE_CODEX_COMMAND_ENTRY,
10130
10254
  {
10131
10255
  name: "skills",
10132
10256
  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"
@@ -10370,6 +10494,142 @@ var startPandaSessionService = async ({
10370
10494
  }
10371
10495
  return !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
10372
10496
  };
10497
+ const isAbsolutePathInsideProject = (projectPath, candidatePath) => {
10498
+ const resolvedProjectPath = path9.resolve(projectPath);
10499
+ const resolvedCandidatePath = path9.resolve(candidatePath);
10500
+ const relativePath = path9.relative(resolvedProjectPath, resolvedCandidatePath);
10501
+ if (!relativePath) {
10502
+ return true;
10503
+ }
10504
+ return !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
10505
+ };
10506
+ const normalizeSessionFilePreviewPath = (value) => normalizeGitWorkspacePath(value?.trim() ?? "").replace(/^\/+|\/+$/g, "");
10507
+ const isMissingPathError = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
10508
+ const resolveSessionFilePreviewPath = async (projectPath, requestedPath) => {
10509
+ const normalizedPath = normalizeSessionFilePreviewPath(requestedPath);
10510
+ const absolutePath = normalizedPath ? path9.resolve(projectPath, normalizedPath) : path9.resolve(projectPath);
10511
+ if (!isAbsolutePathInsideProject(projectPath, absolutePath)) {
10512
+ throw new Error("\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5F53\u524D\u9879\u76EE\u5185\u3002");
10513
+ }
10514
+ let realPath;
10515
+ try {
10516
+ realPath = await fs8.realpath(absolutePath);
10517
+ } catch (error) {
10518
+ if (isMissingPathError(error)) {
10519
+ throw new Error("File not found.");
10520
+ }
10521
+ throw error;
10522
+ }
10523
+ if (!isAbsolutePathInsideProject(projectPath, realPath)) {
10524
+ throw new Error("\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5F53\u524D\u9879\u76EE\u5185\u3002");
10525
+ }
10526
+ return {
10527
+ normalizedPath,
10528
+ absolutePath,
10529
+ realPath
10530
+ };
10531
+ };
10532
+ const normalizeSessionFilePreviewExtension = (value) => {
10533
+ const extension = path9.extname(value).trim().toLowerCase();
10534
+ return extension || null;
10535
+ };
10536
+ const detectSessionFilePreviewKind = (fileName, extension) => {
10537
+ const normalizedName = fileName.trim().toLowerCase();
10538
+ if (extension && SESSION_FILE_PREVIEW_MARKDOWN_EXTENSIONS.has(extension)) {
10539
+ return "markdown";
10540
+ }
10541
+ if (extension && SESSION_FILE_PREVIEW_IMAGE_EXTENSIONS.has(extension)) {
10542
+ return "image";
10543
+ }
10544
+ if (extension && SESSION_FILE_PREVIEW_TEXT_EXTENSIONS.has(extension)) {
10545
+ return "text";
10546
+ }
10547
+ if (extension && SESSION_FILE_PREVIEW_CODE_EXTENSIONS.has(extension) || SESSION_FILE_PREVIEW_CODE_FILENAMES.has(normalizedName) || SESSION_FILE_PREVIEW_CODE_NAME_PATTERNS.some((pattern) => pattern.test(normalizedName))) {
10548
+ return "code";
10549
+ }
10550
+ return null;
10551
+ };
10552
+ const getSessionFilePreviewMimeType = (extension, fileKind) => {
10553
+ if (extension && SESSION_FILE_PREVIEW_MIME_TYPES[extension]) {
10554
+ return SESSION_FILE_PREVIEW_MIME_TYPES[extension];
10555
+ }
10556
+ if (fileKind === "markdown") {
10557
+ return "text/markdown; charset=utf-8";
10558
+ }
10559
+ if (fileKind === "code" || fileKind === "text") {
10560
+ return "text/plain; charset=utf-8";
10561
+ }
10562
+ return null;
10563
+ };
10564
+ const looksLikeTextBuffer = (buffer) => {
10565
+ if (buffer.length === 0) {
10566
+ return true;
10567
+ }
10568
+ let suspiciousControlCount = 0;
10569
+ for (const byte of buffer.values()) {
10570
+ if (byte === 0) {
10571
+ return false;
10572
+ }
10573
+ const isAllowedWhitespace = byte === 9 || byte === 10 || byte === 13;
10574
+ if (!isAllowedWhitespace && byte < 32) {
10575
+ suspiciousControlCount += 1;
10576
+ }
10577
+ }
10578
+ return suspiciousControlCount / buffer.length < 0.08;
10579
+ };
10580
+ const readPreviewBuffer = async (filePath, byteLimit) => {
10581
+ const handle = await fs8.open(filePath, "r");
10582
+ try {
10583
+ const buffer = Buffer.alloc(byteLimit);
10584
+ const { bytesRead } = await handle.read(buffer, 0, byteLimit, 0);
10585
+ return buffer.subarray(0, bytesRead);
10586
+ } finally {
10587
+ await handle.close();
10588
+ }
10589
+ };
10590
+ const readDirectoryHasChildren = async (directoryPath) => {
10591
+ try {
10592
+ const entries = await fs8.readdir(directoryPath);
10593
+ return entries.length > 0;
10594
+ } catch {
10595
+ return false;
10596
+ }
10597
+ };
10598
+ const buildSessionFilePreviewTreeNode = async (projectPath, parentRelativePath, parentRealPath, entry) => {
10599
+ const nextRelativePath = normalizeGitWorkspacePath(
10600
+ parentRelativePath ? `${parentRelativePath}/${entry.name}` : entry.name
10601
+ );
10602
+ const entryPath = path9.join(parentRealPath, entry.name);
10603
+ if (entry.isSymbolicLink()) {
10604
+ try {
10605
+ const realPath = await fs8.realpath(entryPath);
10606
+ if (!isAbsolutePathInsideProject(projectPath, realPath)) {
10607
+ return null;
10608
+ }
10609
+ } catch {
10610
+ return null;
10611
+ }
10612
+ }
10613
+ const stat = await fs8.stat(entryPath).catch(() => null);
10614
+ if (!stat) {
10615
+ return null;
10616
+ }
10617
+ const kind = stat.isDirectory() ? "directory" : stat.isFile() ? "file" : null;
10618
+ if (!kind) {
10619
+ return null;
10620
+ }
10621
+ const extension = kind === "file" ? normalizeSessionFilePreviewExtension(entry.name) : null;
10622
+ const fileKind = kind === "file" ? detectSessionFilePreviewKind(entry.name, extension) ?? "binary" : null;
10623
+ return {
10624
+ path: nextRelativePath,
10625
+ name: entry.name,
10626
+ kind,
10627
+ has_children: kind === "directory" ? await readDirectoryHasChildren(entryPath) : false,
10628
+ extension,
10629
+ file_kind: fileKind,
10630
+ size_bytes: kind === "file" ? stat.size : null
10631
+ };
10632
+ };
10373
10633
  const runGitCommand = async (cwd, args, options) => new Promise((resolve, reject) => {
10374
10634
  let stdout = "";
10375
10635
  let stderr = "";
@@ -11031,17 +11291,36 @@ var startPandaSessionService = async ({
11031
11291
  return null;
11032
11292
  }
11033
11293
  const candidate = value;
11034
- if (candidate.version !== void 0 && candidate.version !== 1) {
11294
+ if (candidate.version !== void 0 && candidate.version !== 1 && candidate.version !== LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION) {
11035
11295
  return null;
11036
11296
  }
11037
11297
  if (!Array.isArray(candidate.visible_commands)) {
11038
11298
  return null;
11039
11299
  }
11040
11300
  return {
11041
- version: 1,
11301
+ version: candidate.version === LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION ? LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION : 1,
11042
11302
  visible_commands: candidate.visible_commands.map((entry) => normalizeVisibleCodexCommandConfigEntry(entry)).filter((entry) => Boolean(entry))
11043
11303
  };
11044
11304
  };
11305
+ const migrateVisibleCodexCommandConfig = (config) => {
11306
+ if (config.version >= LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION) {
11307
+ return config;
11308
+ }
11309
+ const visibleCommands = [...config.visible_commands];
11310
+ const hasReview = visibleCommands.some((entry) => entry.name === REVIEW_VISIBLE_CODEX_COMMAND_ENTRY.name);
11311
+ if (!hasReview) {
11312
+ const skillsIndex = visibleCommands.findIndex((entry) => entry.name === "skills");
11313
+ if (skillsIndex === -1) {
11314
+ visibleCommands.push(REVIEW_VISIBLE_CODEX_COMMAND_ENTRY);
11315
+ } else {
11316
+ visibleCommands.splice(skillsIndex, 0, REVIEW_VISIBLE_CODEX_COMMAND_ENTRY);
11317
+ }
11318
+ }
11319
+ return {
11320
+ version: LATEST_VISIBLE_CODEX_COMMAND_CONFIG_VERSION,
11321
+ visible_commands: visibleCommands
11322
+ };
11323
+ };
11045
11324
  const writeVisibleCodexCommandConfig = async (config) => {
11046
11325
  const configFilePath = await getCodexVisibleCommandsConfigFilePath();
11047
11326
  await fs8.writeFile(configFilePath, `${JSON.stringify(config, null, 2)}
@@ -11053,7 +11332,11 @@ var startPandaSessionService = async ({
11053
11332
  const raw = await fs8.readFile(configFilePath, "utf8");
11054
11333
  const parsed = parseVisibleCodexCommandConfig(JSON.parse(raw));
11055
11334
  if (parsed) {
11056
- return parsed;
11335
+ const migrated = migrateVisibleCodexCommandConfig(parsed);
11336
+ if (migrated !== parsed) {
11337
+ await writeVisibleCodexCommandConfig(migrated);
11338
+ }
11339
+ return migrated;
11057
11340
  }
11058
11341
  } catch {
11059
11342
  }
@@ -11310,6 +11593,36 @@ var startPandaSessionService = async ({
11310
11593
  }
11311
11594
  return `${trimmed.slice(0, maxLength - 1).trimEnd()}\u2026`;
11312
11595
  };
11596
+ const sanitizeGeneratedSessionTitle = (value, maxLength = SESSION_GENERATED_TITLE_MAX_LENGTH) => {
11597
+ 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();
11598
+ if (!normalized) {
11599
+ return null;
11600
+ }
11601
+ if (normalized.length <= maxLength) {
11602
+ return normalized;
11603
+ }
11604
+ return normalized.slice(0, maxLength).trimEnd();
11605
+ };
11606
+ const buildSessionTitleGenerationPrompt = (input) => {
11607
+ const message = input.message.trim();
11608
+ const attachmentNames = input.attachments.map((attachment) => attachment.name.trim()).filter(Boolean).slice(0, 5);
11609
+ const contextParts = [
11610
+ message ? `\u7528\u6237\u9996\u6761\u6D88\u606F\uFF1A
11611
+ ${message}` : null,
11612
+ attachmentNames.length > 0 ? `\u9644\u4EF6\uFF1A
11613
+ ${attachmentNames.map((name) => `- ${name}`).join("\n")}` : null
11614
+ ].filter(Boolean);
11615
+ return [
11616
+ "\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",
11617
+ "\u8981\u6C42\uFF1A",
11618
+ "1. \u53EA\u8F93\u51FA\u6807\u9898\u672C\u8EAB\uFF0C\u4E0D\u8981\u89E3\u91CA\uFF0C\u4E0D\u8981\u52A0\u5F15\u53F7\u3002",
11619
+ "2. \u6807\u9898\u8981\u51C6\u786E\u6982\u62EC\u4EFB\u52A1\u76EE\u6807\uFF0C\u4E0D\u8981\u7167\u6284\u539F\u53E5\u3002",
11620
+ "3. \u5C3D\u91CF\u63A7\u5236\u5728 4 \u5230 14 \u4E2A\u6C49\u5B57\uFF0C\u6216\u7B49\u4EF7\u7684\u77ED\u82F1\u6587\u957F\u5EA6\u3002",
11621
+ "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",
11622
+ "",
11623
+ ...contextParts
11624
+ ].join("\n");
11625
+ };
11313
11626
  const createDiagnosticHash2 = (value) => createHash5("sha1").update(value).digest("hex").slice(0, 16);
11314
11627
  const summarizeTextForDiagnostics = (value, options) => {
11315
11628
  const normalized = typeof value === "string" ? value.trim() : "";
@@ -11347,6 +11660,7 @@ var startPandaSessionService = async ({
11347
11660
  dataHash: attachment.dataHash
11348
11661
  })),
11349
11662
  model: input.model,
11663
+ titleGenerationModel: input.titleGenerationModel ?? null,
11350
11664
  reasoningEffort: input.reasoningEffort,
11351
11665
  requestedServiceTier: input.requestedServiceTier ?? null,
11352
11666
  normalizedRequestedServiceTier: input.normalizedRequestedServiceTier,
@@ -11360,6 +11674,7 @@ var startPandaSessionService = async ({
11360
11674
  prompt: promptSummary,
11361
11675
  attachments: attachmentsSummary,
11362
11676
  model: input.model,
11677
+ titleGenerationModel: input.titleGenerationModel ?? null,
11363
11678
  reasoningEffort: input.reasoningEffort,
11364
11679
  requestedServiceTier: input.requestedServiceTier === "fast" || input.requestedServiceTier === "flex" ? input.requestedServiceTier : input.requestedServiceTier ?? null,
11365
11680
  normalizedRequestedServiceTier: input.normalizedRequestedServiceTier,
@@ -11572,7 +11887,7 @@ var startPandaSessionService = async ({
11572
11887
  return nextOverlayEntries;
11573
11888
  };
11574
11889
  const readTimelineFromRollout = async (sessionId) => {
11575
- const { readCodexTimeline } = await import("./src-SI4UGXXJ.mjs");
11890
+ const { readCodexTimeline } = await import("./src-ILWWSZFE.mjs");
11576
11891
  return readCodexTimeline(sessionId, {
11577
11892
  codexHome,
11578
11893
  sessionFiles: discoveredSessionFiles
@@ -12171,6 +12486,14 @@ var startPandaSessionService = async ({
12171
12486
  }
12172
12487
  return [];
12173
12488
  };
12489
+ const readChangeSetForSession = async (sessionId, changeSetId) => {
12490
+ const changeSets = await readChangeSetsForSession(sessionId);
12491
+ return changeSets.find((entry) => entry.id === changeSetId) ?? null;
12492
+ };
12493
+ const readChangeSetForTurn = async (sessionId, turnId) => {
12494
+ const changeSets = await readChangeSetsForSession(sessionId);
12495
+ return changeSets.find((entry) => entry.turn_id === turnId) ?? null;
12496
+ };
12174
12497
  const readPlanForSession = async (sessionId) => {
12175
12498
  const existing = sessionPlans.get(sessionId);
12176
12499
  if (existing !== void 0) {
@@ -12202,8 +12525,7 @@ var startPandaSessionService = async ({
12202
12525
  return [];
12203
12526
  };
12204
12527
  const readChangeSetFileDiffForSession = async (sessionId, input) => {
12205
- const changeSets = await readChangeSetsForSession(sessionId);
12206
- const changeSet = changeSets.find((entry) => entry.id === input.changeSetId);
12528
+ const changeSet = await readChangeSetForSession(sessionId, input.changeSetId);
12207
12529
  if (!changeSet) {
12208
12530
  return null;
12209
12531
  }
@@ -12676,7 +12998,7 @@ var startPandaSessionService = async ({
12676
12998
  commands: filterCodexCommandsForDisplay(catalog.commands, visibleConfig)
12677
12999
  };
12678
13000
  };
12679
- const renameSessionFromCommand = async (sessionId, nextName) => {
13001
+ const renameSession = async (sessionId, nextName) => {
12680
13002
  if (managedSessions.has(sessionId)) {
12681
13003
  const managedSession = managedSessions.get(sessionId);
12682
13004
  managedSessions.set(sessionId, {
@@ -12701,6 +13023,37 @@ var startPandaSessionService = async ({
12701
13023
  }
12702
13024
  });
12703
13025
  };
13026
+ const generateSessionTitleInBackground = async (input) => {
13027
+ const prompt = buildSessionTitleGenerationPrompt({
13028
+ message: input.message,
13029
+ attachments: input.attachments
13030
+ });
13031
+ try {
13032
+ const generated = await liveSessionStream.runOneShotPrompt({
13033
+ cwd: input.project.path,
13034
+ prompt,
13035
+ model: input.model,
13036
+ timeoutMs: SESSION_TITLE_GENERATION_TIMEOUT_MS
13037
+ });
13038
+ const nextTitle = sanitizeGeneratedSessionTitle(generated);
13039
+ if (!nextTitle || nextTitle === input.fallbackTitle) {
13040
+ return;
13041
+ }
13042
+ const currentSession = await findSnapshotSession(input.sessionId);
13043
+ if (!currentSession || currentSession.title !== input.fallbackTitle) {
13044
+ return;
13045
+ }
13046
+ await renameSession(input.sessionId, nextTitle);
13047
+ } catch (error) {
13048
+ diagnosticLogger.warn({
13049
+ sessionId: input.sessionId,
13050
+ projectId: input.project.id,
13051
+ projectPath: input.project.path,
13052
+ model: input.model,
13053
+ error: error instanceof Error ? error.message : "Unknown error"
13054
+ }, "Failed to generate background session title.");
13055
+ }
13056
+ };
12704
13057
  const executeSessionCommand = async (session, project, rawInput) => {
12705
13058
  const parsed = parseSlashCommandInput(rawInput);
12706
13059
  if (!parsed) {
@@ -12802,7 +13155,7 @@ var startPandaSessionService = async ({
12802
13155
  }
12803
13156
  if (command.name === "rename") {
12804
13157
  if (parsed.args) {
12805
- await renameSessionFromCommand(session.id, parsed.args);
13158
+ await renameSession(session.id, parsed.args);
12806
13159
  return createCommandPanel({
12807
13160
  sessionId: session.id,
12808
13161
  commandName: command.name,
@@ -12872,7 +13225,7 @@ var startPandaSessionService = async ({
12872
13225
  if (!nextName) {
12873
13226
  throw new Error("\u8BF7\u8F93\u5165\u65B0\u7684\u4F1A\u8BDD\u540D\u79F0\u3002");
12874
13227
  }
12875
- await renameSessionFromCommand(input.sessionId, nextName);
13228
+ await renameSession(input.sessionId, nextName);
12876
13229
  clearStoredCommandPanel(input.panelId);
12877
13230
  return {
12878
13231
  ...stored.panel,
@@ -13040,7 +13393,7 @@ var startPandaSessionService = async ({
13040
13393
  lastSnapshotRefreshAt = Date.now();
13041
13394
  return snapshot;
13042
13395
  }
13043
- const { discoverLocalCodexData } = await import("./src-SI4UGXXJ.mjs");
13396
+ const { discoverLocalCodexData } = await import("./src-ILWWSZFE.mjs");
13044
13397
  const discovery = await discoverLocalCodexData({
13045
13398
  agentId: localAgentId,
13046
13399
  agentName: localAgentName,
@@ -13116,6 +13469,135 @@ var startPandaSessionService = async ({
13116
13469
  return snapshot.projects.find((project) => project.id === projectId) ?? (await buildSnapshot({ force: true })).projects.find((project) => project.id === projectId) ?? null;
13117
13470
  };
13118
13471
  const readProjectForSession = async (session) => findSnapshotProject(session.project_id);
13472
+ const readSessionFilePreviewTree = async (session, project, requestedPath) => {
13473
+ const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
13474
+ const stat = await fs8.stat(resolved.realPath).catch((error) => {
13475
+ if (isMissingPathError(error)) {
13476
+ throw new Error("File not found.");
13477
+ }
13478
+ throw error;
13479
+ });
13480
+ if (!stat.isDirectory()) {
13481
+ throw new Error("\u76EE\u6807\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\u3002");
13482
+ }
13483
+ const entries = await fs8.readdir(resolved.realPath, {
13484
+ withFileTypes: true
13485
+ });
13486
+ const nodes = (await Promise.all(
13487
+ entries.map(
13488
+ (entry) => buildSessionFilePreviewTreeNode(
13489
+ project.path,
13490
+ resolved.normalizedPath,
13491
+ resolved.realPath,
13492
+ entry
13493
+ )
13494
+ )
13495
+ )).filter((entry) => Boolean(entry)).sort((left, right) => {
13496
+ if (left.kind !== right.kind) {
13497
+ return left.kind === "directory" ? -1 : 1;
13498
+ }
13499
+ return left.name.localeCompare(right.name, "zh-CN");
13500
+ });
13501
+ return {
13502
+ session_id: session.id,
13503
+ project_id: project.id,
13504
+ root_path: SESSION_FILE_PREVIEW_ROOT_PATH,
13505
+ parent_path: resolved.normalizedPath || null,
13506
+ nodes,
13507
+ loaded_at: isoNow5()
13508
+ };
13509
+ };
13510
+ const readSessionFilePreviewContent = async (session, project, requestedPath) => {
13511
+ const resolved = await resolveSessionFilePreviewPath(project.path, requestedPath);
13512
+ const stat = await fs8.stat(resolved.realPath).catch((error) => {
13513
+ if (isMissingPathError(error)) {
13514
+ throw new Error("File not found.");
13515
+ }
13516
+ throw error;
13517
+ });
13518
+ if (!stat.isFile()) {
13519
+ throw new Error("\u76EE\u6807\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6\u3002");
13520
+ }
13521
+ const fileName = path9.basename(resolved.normalizedPath || resolved.realPath);
13522
+ const extension = normalizeSessionFilePreviewExtension(fileName);
13523
+ let fileKind = detectSessionFilePreviewKind(fileName, extension) ?? null;
13524
+ let previewBuffer = null;
13525
+ if (!fileKind || fileKind === "binary") {
13526
+ previewBuffer = await readPreviewBuffer(
13527
+ resolved.realPath,
13528
+ Math.min(SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT, Math.max(1024, stat.size))
13529
+ );
13530
+ fileKind = looksLikeTextBuffer(previewBuffer) ? "text" : "binary";
13531
+ }
13532
+ if (fileKind === "image") {
13533
+ if (stat.size > SESSION_FILE_PREVIEW_IMAGE_BYTE_LIMIT) {
13534
+ return {
13535
+ session_id: session.id,
13536
+ project_id: project.id,
13537
+ path: resolved.normalizedPath,
13538
+ name: fileName,
13539
+ extension,
13540
+ file_kind: fileKind,
13541
+ mime_type: getSessionFilePreviewMimeType(extension, fileKind),
13542
+ size_bytes: stat.size,
13543
+ encoding: null,
13544
+ is_truncated: true,
13545
+ content_text: null,
13546
+ content_base64: null,
13547
+ loaded_at: isoNow5()
13548
+ };
13549
+ }
13550
+ const buffer2 = await fs8.readFile(resolved.realPath);
13551
+ return {
13552
+ session_id: session.id,
13553
+ project_id: project.id,
13554
+ path: resolved.normalizedPath,
13555
+ name: fileName,
13556
+ extension,
13557
+ file_kind: fileKind,
13558
+ mime_type: getSessionFilePreviewMimeType(extension, fileKind),
13559
+ size_bytes: stat.size,
13560
+ encoding: "base64",
13561
+ is_truncated: false,
13562
+ content_text: null,
13563
+ content_base64: buffer2.toString("base64"),
13564
+ loaded_at: isoNow5()
13565
+ };
13566
+ }
13567
+ if (fileKind === "binary") {
13568
+ return {
13569
+ session_id: session.id,
13570
+ project_id: project.id,
13571
+ path: resolved.normalizedPath,
13572
+ name: fileName,
13573
+ extension,
13574
+ file_kind: fileKind,
13575
+ mime_type: getSessionFilePreviewMimeType(extension, fileKind),
13576
+ size_bytes: stat.size,
13577
+ encoding: null,
13578
+ is_truncated: false,
13579
+ content_text: null,
13580
+ content_base64: null,
13581
+ loaded_at: isoNow5()
13582
+ };
13583
+ }
13584
+ const buffer = previewBuffer ?? await readPreviewBuffer(resolved.realPath, SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT);
13585
+ return {
13586
+ session_id: session.id,
13587
+ project_id: project.id,
13588
+ path: resolved.normalizedPath,
13589
+ name: fileName,
13590
+ extension,
13591
+ file_kind: fileKind,
13592
+ mime_type: getSessionFilePreviewMimeType(extension, fileKind),
13593
+ size_bytes: stat.size,
13594
+ encoding: "utf8",
13595
+ is_truncated: stat.size > SESSION_FILE_PREVIEW_TEXT_BYTE_LIMIT,
13596
+ content_text: buffer.toString("utf8"),
13597
+ content_base64: null,
13598
+ loaded_at: isoNow5()
13599
+ };
13600
+ };
13119
13601
  const validateHubControlPlaneAuth = (request) => {
13120
13602
  const expectedApiKey = process.env.PANDA_HUB_API_KEY?.trim() ?? "";
13121
13603
  if (!expectedApiKey) {
@@ -13147,6 +13629,58 @@ var startPandaSessionService = async ({
13147
13629
  }
13148
13630
  await runGitCommand(projectPath, ["restore", "--source=HEAD", "--staged", "--worktree", "--", ...targetPaths]);
13149
13631
  };
13632
+ const normalizeRollbackPatchText = (input) => {
13633
+ const normalized = input.replace(/\r\n/g, "\n").trim();
13634
+ return normalized ? `${normalized}
13635
+ ` : "";
13636
+ };
13637
+ const buildRollbackPatchText = (changeSet) => {
13638
+ if (changeSet.aggregated_diff.trim()) {
13639
+ return normalizeRollbackPatchText(changeSet.aggregated_diff);
13640
+ }
13641
+ if (changeSet.files.some((file) => file.diff.trim().length === 0)) {
13642
+ return "";
13643
+ }
13644
+ return normalizeRollbackPatchText(
13645
+ changeSet.files.map((file) => file.diff).filter((diff) => diff.trim().length > 0).join("\n\n")
13646
+ );
13647
+ };
13648
+ const ensureChangeSetPathsAreInProject = (projectPath, changeSet) => {
13649
+ for (const file of changeSet.files) {
13650
+ const candidates = [file.path, file.move_path].filter((value) => Boolean(value?.trim()));
13651
+ for (const candidate of candidates) {
13652
+ if (!isPathInsideProject(projectPath, candidate)) {
13653
+ 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");
13654
+ }
13655
+ }
13656
+ }
13657
+ };
13658
+ const rollbackSessionChangeSet = async (projectPath, changeSet) => {
13659
+ if (changeSet.status !== "completed") {
13660
+ throw new Error("\u5F53\u524D\u8FD9\u8F6E\u6539\u52A8\u5C1A\u672A\u5B8C\u6210\uFF0C\u6682\u65F6\u4E0D\u80FD\u56DE\u6EDA\u3002");
13661
+ }
13662
+ if (changeSet.files.length === 0) {
13663
+ throw new Error("\u8FD9\u8F6E\u5BF9\u8BDD\u6CA1\u6709\u53EF\u56DE\u6EDA\u7684\u6587\u4EF6\u6539\u52A8\u3002");
13664
+ }
13665
+ ensureChangeSetPathsAreInProject(projectPath, changeSet);
13666
+ const patchText = buildRollbackPatchText(changeSet);
13667
+ if (!patchText) {
13668
+ throw new Error("\u8FD9\u8F6E\u6539\u52A8\u7F3A\u5C11\u53EF\u9006\u8865\u4E01\uFF0C\u6682\u65F6\u65E0\u6CD5\u56DE\u6EDA\u3002");
13669
+ }
13670
+ const tempDir = await fs8.mkdtemp(path9.join(os6.tmpdir(), "panda-change-set-rollback-"));
13671
+ const patchFilePath = path9.join(tempDir, "rollback.patch");
13672
+ await fs8.writeFile(patchFilePath, patchText, "utf8");
13673
+ try {
13674
+ const patchArgs = ["apply", "--reverse", "--whitespace=nowarn", "--binary", patchFilePath];
13675
+ await runGitCommand(projectPath, [...patchArgs.slice(0, 1), "--check", ...patchArgs.slice(1)]);
13676
+ await runGitCommand(projectPath, patchArgs);
13677
+ } catch (error) {
13678
+ 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";
13679
+ throw new Error(`\u65E0\u6CD5\u5B89\u5168\u56DE\u6EDA\u8FD9\u8F6E\u6539\u52A8\uFF1A${reason}`);
13680
+ } finally {
13681
+ await fs8.rm(tempDir, { recursive: true, force: true }).catch(() => void 0);
13682
+ }
13683
+ };
13150
13684
  const discardAllGitWorkspaceChanges = async (projectPath) => {
13151
13685
  await runGitCommand(projectPath, ["restore", "--source=HEAD", "--staged", "--worktree", "--", "."]);
13152
13686
  await runGitCommand(projectPath, ["clean", "-fd", "--", "."], {
@@ -13851,6 +14385,107 @@ var startPandaSessionService = async ({
13851
14385
  }
13852
14386
  return diff;
13853
14387
  });
14388
+ app.post("/api/sessions/:sessionId/turns/:turnId/actions", async (request, reply) => {
14389
+ const { sessionId, turnId } = request.params;
14390
+ const session = await findSnapshotSession(sessionId);
14391
+ if (!session) {
14392
+ reply.code(404);
14393
+ return { error: "Session not found." };
14394
+ }
14395
+ if (!session.capability.can_show_git) {
14396
+ reply.code(409);
14397
+ return { error: "This session does not support git workspace actions." };
14398
+ }
14399
+ const project = await readProjectForSession(session);
14400
+ if (!project) {
14401
+ reply.code(404);
14402
+ return { error: "Project not found." };
14403
+ }
14404
+ if (request.body.action !== "rollback") {
14405
+ reply.code(409);
14406
+ return { error: "Unsupported turn action." };
14407
+ }
14408
+ const normalizedTurnId = turnId.trim();
14409
+ if (!normalizedTurnId) {
14410
+ reply.code(400);
14411
+ return { error: "Turn id is required." };
14412
+ }
14413
+ const changeSet = await readChangeSetForTurn(sessionId, normalizedTurnId);
14414
+ if (!changeSet) {
14415
+ reply.code(404);
14416
+ return { error: "Session turn change-set not found." };
14417
+ }
14418
+ try {
14419
+ await rollbackSessionChangeSet(project.path, changeSet);
14420
+ return {
14421
+ ok: true,
14422
+ turn_id: changeSet.turn_id,
14423
+ change_set_id: changeSet.id,
14424
+ workspace: await readSessionGitWorkspace(session, project)
14425
+ };
14426
+ } catch (error) {
14427
+ reply.code(409);
14428
+ return {
14429
+ error: error instanceof Error ? error.message : "Unable to execute turn action."
14430
+ };
14431
+ }
14432
+ });
14433
+ app.get("/api/sessions/:sessionId/file-preview/tree", async (request, reply) => {
14434
+ const { sessionId } = request.params;
14435
+ const { path: requestedPath } = request.query ?? {};
14436
+ const session = await findSnapshotSession(sessionId);
14437
+ if (!session) {
14438
+ reply.code(404);
14439
+ return { error: "Session not found." };
14440
+ }
14441
+ const project = await readProjectForSession(session);
14442
+ if (!project) {
14443
+ reply.code(404);
14444
+ return { error: "Project not found." };
14445
+ }
14446
+ try {
14447
+ return await readSessionFilePreviewTree(session, project, requestedPath ?? null);
14448
+ } catch (error) {
14449
+ if (error instanceof Error && error.message === "File not found.") {
14450
+ reply.code(404);
14451
+ return { error: error.message };
14452
+ }
14453
+ reply.code(409);
14454
+ return {
14455
+ error: error instanceof Error ? error.message : "Unable to read file preview tree."
14456
+ };
14457
+ }
14458
+ });
14459
+ app.get("/api/sessions/:sessionId/file-preview/content", async (request, reply) => {
14460
+ const { sessionId } = request.params;
14461
+ const { path: requestedPath } = request.query ?? {};
14462
+ const session = await findSnapshotSession(sessionId);
14463
+ if (!session) {
14464
+ reply.code(404);
14465
+ return { error: "Session not found." };
14466
+ }
14467
+ if (!requestedPath?.trim()) {
14468
+ reply.code(400);
14469
+ return { error: "path is required." };
14470
+ }
14471
+ const project = await readProjectForSession(session);
14472
+ if (!project) {
14473
+ reply.code(404);
14474
+ return { error: "Project not found." };
14475
+ }
14476
+ try {
14477
+ return await readSessionFilePreviewContent(session, project, requestedPath);
14478
+ } catch (error) {
14479
+ if (error instanceof Error && error.message === "File not found.") {
14480
+ reply.code(404);
14481
+ return { error: error.message };
14482
+ }
14483
+ reply.code(409);
14484
+ return {
14485
+ error: error instanceof Error ? error.message : "Unable to read file preview content."
14486
+ };
14487
+ }
14488
+ });
13854
14489
  app.get("/api/sessions/:sessionId/git-workspace", async (request, reply) => {
13855
14490
  const { sessionId } = request.params;
13856
14491
  const session = await findSnapshotSession(sessionId);
@@ -14551,6 +15186,7 @@ var startPandaSessionService = async ({
14551
15186
  const input = request.body.input?.trim() ?? "";
14552
15187
  const attachments = normalizeSessionInputAttachments(request.body.attachments);
14553
15188
  const model = request.body.model?.trim() || null;
15189
+ const titleGenerationModel = request.body.titleGenerationModel?.trim() || DEFAULT_SESSION_TITLE_GENERATION_MODEL;
14554
15190
  const reasoningEffort = request.body.reasoningEffort?.trim() || null;
14555
15191
  const requestedServiceTier = request.body.serviceTier;
14556
15192
  const normalizedRequestedServiceTier = normalizeServiceTier(requestedServiceTier);
@@ -14579,6 +15215,7 @@ var startPandaSessionService = async ({
14579
15215
  prompt,
14580
15216
  attachments,
14581
15217
  model,
15218
+ titleGenerationModel,
14582
15219
  reasoningEffort,
14583
15220
  requestedServiceTier,
14584
15221
  normalizedRequestedServiceTier,
@@ -14672,6 +15309,14 @@ var startPandaSessionService = async ({
14672
15309
  managedSessions.set(session.id, session);
14673
15310
  upsertSnapshotSession(session);
14674
15311
  broadcastSnapshotChanged();
15312
+ void generateSessionTitleInBackground({
15313
+ sessionId: session.id,
15314
+ project,
15315
+ fallbackTitle: title,
15316
+ message: input,
15317
+ attachments,
15318
+ model: titleGenerationModel
15319
+ });
14675
15320
  reply.code(201);
14676
15321
  return { session };
14677
15322
  });
@@ -14695,29 +15340,7 @@ var startPandaSessionService = async ({
14695
15340
  reply.code(400);
14696
15341
  return { error: "Session name is required." };
14697
15342
  }
14698
- if (managedSessions.has(sessionId)) {
14699
- const managedSession = managedSessions.get(sessionId);
14700
- managedSessions.set(sessionId, {
14701
- ...managedSession,
14702
- title: nextName
14703
- });
14704
- }
14705
- await liveSessionStream.setThreadName(sessionId, nextName).catch(() => {
14706
- });
14707
- await appendSessionIndexUpdate(
14708
- sessionId,
14709
- {
14710
- thread_name: nextName
14711
- },
14712
- codexHome
14713
- );
14714
- patchSnapshotSession(sessionId, { title: nextName });
14715
- broadcastEvent("session.updated", {
14716
- sessionId,
14717
- sessionPatch: {
14718
- title: nextName
14719
- }
14720
- });
15343
+ await renameSession(sessionId, nextName);
14721
15344
  return {
14722
15345
  ok: true,
14723
15346
  affectedSessionIds: [sessionId],