@kimuson/claude-code-viewer 0.5.7 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  // src/server/main.ts
4
4
  import { Command as Command3 } from "commander";
5
- import { Effect as Effect49 } from "effect";
5
+ import { Effect as Effect53 } from "effect";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@kimuson/claude-code-viewer",
10
- version: "0.5.7",
10
+ version: "0.5.9",
11
11
  description: "A full-featured web-based Claude Code client that provides complete interactive functionality for managing Claude Code projects.",
12
12
  type: "module",
13
13
  license: "MIT",
@@ -51,7 +51,7 @@ var package_default = {
51
51
  prepare: "lefthook install"
52
52
  },
53
53
  dependencies: {
54
- "@anthropic-ai/claude-agent-sdk": "0.2.19",
54
+ "@anthropic-ai/claude-agent-sdk": "0.2.20",
55
55
  "@anthropic-ai/claude-code": "2.0.24",
56
56
  "@anthropic-ai/sdk": "0.71.2",
57
57
  "@effect/cluster": "0.56.1",
@@ -219,7 +219,7 @@ import { resolve as resolve6 } from "node:path";
219
219
  import { NodeContext as NodeContext2 } from "@effect/platform-node";
220
220
  import { serve } from "@hono/node-server";
221
221
  import { serveStatic } from "@hono/node-server/serve-static";
222
- import { Effect as Effect48 } from "effect";
222
+ import { Effect as Effect52, Layer as Layer43 } from "effect";
223
223
 
224
224
  // src/server/core/agent-session/index.ts
225
225
  import { Layer as Layer3 } from "effect";
@@ -1077,12 +1077,28 @@ var ProjectRepository = class extends Context7.Tag("ProjectRepository")() {
1077
1077
  // src/server/core/claude-code/functions/scanCommandFiles.ts
1078
1078
  import { FileSystem as FileSystem5, Path as Path6 } from "@effect/platform";
1079
1079
  import { Effect as Effect10 } from "effect";
1080
+ var parseCommandFrontmatter = (content) => {
1081
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1082
+ if (!frontmatterMatch?.[1]) {
1083
+ return { description: null, argumentHint: null };
1084
+ }
1085
+ const frontmatter = frontmatterMatch[1];
1086
+ const descriptionMatch = frontmatter.match(
1087
+ /^description:\s*['"]?([^'"\n]+)['"]?\s*$/m
1088
+ );
1089
+ const description = descriptionMatch?.[1]?.trim() ?? null;
1090
+ const argumentHintMatch = frontmatter.match(
1091
+ /^argument-hint:\s*['"]?([^'"\n]+)['"]?\s*$/m
1092
+ );
1093
+ const argumentHint = argumentHintMatch?.[1]?.trim() ?? null;
1094
+ return { description, argumentHint };
1095
+ };
1080
1096
  var pathToCommandName = (filePath, baseDir) => {
1081
1097
  const normalizedBaseDir = baseDir.endsWith("/") ? baseDir.slice(0, -1) : baseDir;
1082
1098
  const relativePath = filePath.startsWith(normalizedBaseDir) ? filePath.slice(normalizedBaseDir.length + 1) : filePath;
1083
1099
  return relativePath.replace(/\.md$/, "").replace(/\//g, ":");
1084
1100
  };
1085
- var scanCommandFilesRecursively = (dirPath) => Effect10.gen(function* () {
1101
+ var scanCommandFilesWithMetadata = (dirPath) => Effect10.gen(function* () {
1086
1102
  const fs = yield* FileSystem5.FileSystem;
1087
1103
  const path = yield* Path6.Path;
1088
1104
  const scanDirectory = (currentPath) => Effect10.gen(function* () {
@@ -1103,7 +1119,10 @@ var scanCommandFilesRecursively = (dirPath) => Effect10.gen(function* () {
1103
1119
  return yield* scanDirectory(itemPath);
1104
1120
  }
1105
1121
  if (info.type === "File" && item.endsWith(".md")) {
1106
- return [pathToCommandName(itemPath, dirPath)];
1122
+ const content = yield* fs.readFileString(itemPath);
1123
+ const { description, argumentHint } = parseCommandFrontmatter(content);
1124
+ const name = pathToCommandName(itemPath, dirPath);
1125
+ return [{ name, description, argumentHint }];
1107
1126
  }
1108
1127
  return [];
1109
1128
  }),
@@ -1118,7 +1137,7 @@ var scanCommandFilesRecursively = (dirPath) => Effect10.gen(function* () {
1118
1137
  })
1119
1138
  );
1120
1139
  });
1121
- var scanSkillFilesRecursively = (dirPath) => Effect10.gen(function* () {
1140
+ var scanSkillFilesWithMetadata = (dirPath) => Effect10.gen(function* () {
1122
1141
  const fs = yield* FileSystem5.FileSystem;
1123
1142
  const path = yield* Path6.Path;
1124
1143
  const scanDirectory = (currentPath, relativePath) => Effect10.gen(function* () {
@@ -1128,11 +1147,13 @@ var scanSkillFilesRecursively = (dirPath) => Effect10.gen(function* () {
1128
1147
  }
1129
1148
  const skillFilePath = path.join(currentPath, "SKILL.md");
1130
1149
  const skillFileExists = yield* fs.exists(skillFilePath);
1131
- const skillNames = [];
1150
+ const skills = [];
1132
1151
  if (skillFileExists) {
1133
1152
  const skillName = relativePath.replace(/\//g, ":");
1134
1153
  if (skillName) {
1135
- skillNames.push(skillName);
1154
+ const content = yield* fs.readFileString(skillFilePath);
1155
+ const { description, argumentHint } = parseCommandFrontmatter(content);
1156
+ skills.push({ name: skillName, description, argumentHint });
1136
1157
  }
1137
1158
  }
1138
1159
  const items = yield* fs.readDirectory(currentPath);
@@ -1152,7 +1173,7 @@ var scanSkillFilesRecursively = (dirPath) => Effect10.gen(function* () {
1152
1173
  }),
1153
1174
  { concurrency: "unbounded" }
1154
1175
  );
1155
- return [...skillNames, ...results.flat()];
1176
+ return [...skills, ...results.flat()];
1156
1177
  });
1157
1178
  return yield* scanDirectory(dirPath, "").pipe(
1158
1179
  Effect10.match({
@@ -1203,9 +1224,15 @@ var parseMcpListOutput = (output) => {
1203
1224
  if (colonIndex > 0) {
1204
1225
  const name = line.substring(0, colonIndex).trim();
1205
1226
  const rest = line.substring(colonIndex + 1).trim();
1227
+ let status = "unknown";
1228
+ if (rest.includes("\u2713") || rest.toLowerCase().includes("connected")) {
1229
+ status = "connected";
1230
+ } else if (rest.includes("\u2717") || rest.toLowerCase().includes("failed")) {
1231
+ status = "failed";
1232
+ }
1206
1233
  const command = rest.replace(/\s*-\s*[✓✗].*$/, "").trim();
1207
1234
  if (name && command) {
1208
- servers.push({ name, command });
1235
+ servers.push({ name, command, status });
1209
1236
  }
1210
1237
  }
1211
1238
  }
@@ -1437,25 +1464,55 @@ var LayerImpl9 = Effect13.gen(function* () {
1437
1464
  const { projectId } = options;
1438
1465
  const { project } = yield* projectRepository.getProject(projectId);
1439
1466
  const features = yield* claudeCodeService.getAvailableFeatures();
1440
- const globalCommands = yield* scanCommandFilesRecursively(
1467
+ const globalCommands = yield* scanCommandFilesWithMetadata(
1441
1468
  (yield* context.claudeCodePaths).claudeCommandsDirPath
1442
1469
  );
1443
- const projectCommands = project.meta.projectPath === null ? [] : yield* scanCommandFilesRecursively(
1470
+ const projectCommands = project.meta.projectPath === null ? [] : yield* scanCommandFilesWithMetadata(
1444
1471
  path.resolve(project.meta.projectPath, ".claude", "commands")
1445
1472
  );
1446
- const globalSkills = features.runSkillsDirectly ? yield* scanSkillFilesRecursively(
1473
+ const globalSkills = features.runSkillsDirectly ? yield* scanSkillFilesWithMetadata(
1447
1474
  (yield* context.claudeCodePaths).claudeSkillsDirPath
1448
1475
  ) : [];
1449
- const projectSkills = features.runSkillsDirectly && project.meta.projectPath !== null ? yield* scanSkillFilesRecursively(
1476
+ const projectSkills = features.runSkillsDirectly && project.meta.projectPath !== null ? yield* scanSkillFilesWithMetadata(
1450
1477
  path.resolve(project.meta.projectPath, ".claude", "skills")
1451
1478
  ) : [];
1479
+ const defaultCommands = [
1480
+ {
1481
+ name: "init",
1482
+ description: "Initialize Claude Code in current project",
1483
+ argumentHint: null
1484
+ },
1485
+ {
1486
+ name: "compact",
1487
+ description: "Compact conversation history",
1488
+ argumentHint: null
1489
+ },
1490
+ {
1491
+ name: "security-review",
1492
+ description: "Review code for security issues",
1493
+ argumentHint: null
1494
+ },
1495
+ {
1496
+ name: "review",
1497
+ description: "Review code changes",
1498
+ argumentHint: null
1499
+ }
1500
+ ];
1501
+ const toNames = (commands) => commands.map((c) => c.name);
1452
1502
  return {
1453
1503
  response: {
1504
+ // New format: CommandInfo[] with metadata
1454
1505
  globalCommands,
1455
1506
  projectCommands,
1456
1507
  globalSkills,
1457
1508
  projectSkills,
1458
- defaultCommands: ["init", "compact", "security-review", "review"]
1509
+ defaultCommands,
1510
+ // Legacy format: string[] for backward compatibility
1511
+ globalCommandsLegacy: toNames(globalCommands),
1512
+ projectCommandsLegacy: toNames(projectCommands),
1513
+ globalSkillsLegacy: toNames(globalSkills),
1514
+ projectSkillsLegacy: toNames(projectSkills),
1515
+ defaultCommandsLegacy: toNames(defaultCommands)
1459
1516
  },
1460
1517
  status: 200
1461
1518
  };
@@ -1751,7 +1808,8 @@ var LayerImpl12 = Effect17.gen(function* () {
1751
1808
  permissionMode: "default",
1752
1809
  locale: DEFAULT_LOCALE,
1753
1810
  theme: "system",
1754
- searchHotkey: "command-k"
1811
+ searchHotkey: "command-k",
1812
+ autoScheduleContinueOnRateLimit: false
1755
1813
  });
1756
1814
  const setUserConfig = (newConfig) => Effect17.gen(function* () {
1757
1815
  yield* Ref5.update(configRef, () => newConfig);
@@ -5254,6 +5312,10 @@ var ProjectController = class extends Context28.Tag("ProjectController")() {
5254
5312
  }
5255
5313
  };
5256
5314
 
5315
+ // src/server/core/rate-limit/services/RateLimitAutoScheduleService.ts
5316
+ import { FileSystem as FileSystem14, Path as Path17 } from "@effect/platform";
5317
+ import { Context as Context31, Effect as Effect40, Layer as Layer33, Ref as Ref12 } from "effect";
5318
+
5257
5319
  // src/server/core/scheduler/config.ts
5258
5320
  import { homedir as homedir4 } from "node:os";
5259
5321
  import { FileSystem as FileSystem12, Path as Path16 } from "@effect/platform";
@@ -5675,18 +5737,321 @@ var SchedulerService = class extends Context30.Tag("SchedulerService")() {
5675
5737
  }
5676
5738
  };
5677
5739
 
5740
+ // src/server/core/rate-limit/schema.ts
5741
+ import { z as z25 } from "zod";
5742
+ var RateLimitEntrySchema = z25.object({
5743
+ type: z25.literal("assistant"),
5744
+ error: z25.literal("rate_limit"),
5745
+ isApiErrorMessage: z25.literal(true),
5746
+ sessionId: z25.string(),
5747
+ message: z25.object({
5748
+ content: z25.array(
5749
+ z25.object({
5750
+ type: z25.literal("text"),
5751
+ text: z25.string()
5752
+ })
5753
+ )
5754
+ })
5755
+ });
5756
+
5757
+ // src/server/core/rate-limit/functions/detectRateLimitFromLastLine.ts
5758
+ var detectRateLimitFromLastLine = (jsonLine) => {
5759
+ const trimmed = jsonLine.trim();
5760
+ if (trimmed === "") {
5761
+ return { detected: false };
5762
+ }
5763
+ let parsed;
5764
+ try {
5765
+ parsed = JSON.parse(trimmed);
5766
+ } catch {
5767
+ return { detected: false };
5768
+ }
5769
+ const validation = RateLimitEntrySchema.safeParse(parsed);
5770
+ if (!validation.success) {
5771
+ return { detected: false };
5772
+ }
5773
+ const entry = validation.data;
5774
+ const firstTextContent = entry.message.content[0];
5775
+ if (!firstTextContent) {
5776
+ return { detected: false };
5777
+ }
5778
+ return {
5779
+ detected: true,
5780
+ sessionId: entry.sessionId,
5781
+ resetTimeText: firstTextContent.text
5782
+ };
5783
+ };
5784
+
5785
+ // src/server/core/rate-limit/functions/parseRateLimitResetTime.ts
5786
+ var parseRateLimitResetTime = (resetTimeText) => {
5787
+ const pattern = /resets\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm|AM|PM)\s*\(([^)]+)\)/i;
5788
+ const match = pattern.exec(resetTimeText);
5789
+ if (!match) {
5790
+ return getFallbackTime();
5791
+ }
5792
+ const hoursStr = match[1];
5793
+ const minutesStr = match[2];
5794
+ const meridiem = match[3];
5795
+ const timezone = match[4];
5796
+ if (hoursStr === void 0 || meridiem === void 0 || timezone === void 0) {
5797
+ return getFallbackTime();
5798
+ }
5799
+ const hours = Number.parseInt(hoursStr, 10);
5800
+ const minutes = minutesStr !== void 0 ? Number.parseInt(minutesStr, 10) : 0;
5801
+ if (Number.isNaN(hours) || Number.isNaN(minutes)) {
5802
+ return getFallbackTime();
5803
+ }
5804
+ const hours24 = convertTo24Hour(hours, meridiem.toLowerCase());
5805
+ const resetDate = createDateInTimezone(hours24, minutes, timezone);
5806
+ if (resetDate === null) {
5807
+ return getFallbackTime();
5808
+ }
5809
+ resetDate.setMinutes(resetDate.getMinutes() + 1);
5810
+ return resetDate.toISOString();
5811
+ };
5812
+ var convertTo24Hour = (hours, meridiem) => {
5813
+ const isPM = meridiem === "pm";
5814
+ if (hours === 12) {
5815
+ return isPM ? 12 : 0;
5816
+ }
5817
+ return isPM ? hours + 12 : hours;
5818
+ };
5819
+ var getTimezoneOffsetMinutes = (timezone, date) => {
5820
+ const utcFormatter = new Intl.DateTimeFormat("en-US", {
5821
+ timeZone: "UTC",
5822
+ year: "numeric",
5823
+ month: "2-digit",
5824
+ day: "2-digit",
5825
+ hour: "2-digit",
5826
+ minute: "2-digit",
5827
+ hour12: false
5828
+ });
5829
+ const tzFormatter = new Intl.DateTimeFormat("en-US", {
5830
+ timeZone: timezone,
5831
+ year: "numeric",
5832
+ month: "2-digit",
5833
+ day: "2-digit",
5834
+ hour: "2-digit",
5835
+ minute: "2-digit",
5836
+ hour12: false
5837
+ });
5838
+ const utcParts = utcFormatter.formatToParts(date);
5839
+ const tzParts = tzFormatter.formatToParts(date);
5840
+ const extractDateTime = (parts) => ({
5841
+ day: Number.parseInt(parts.find((p) => p.type === "day")?.value ?? "0", 10),
5842
+ hour: Number.parseInt(
5843
+ parts.find((p) => p.type === "hour")?.value ?? "0",
5844
+ 10
5845
+ ),
5846
+ minute: Number.parseInt(
5847
+ parts.find((p) => p.type === "minute")?.value ?? "0",
5848
+ 10
5849
+ )
5850
+ });
5851
+ const utc = extractDateTime(utcParts);
5852
+ const tz = extractDateTime(tzParts);
5853
+ let dayDiff = tz.day - utc.day;
5854
+ if (dayDiff > 15) dayDiff -= 31;
5855
+ if (dayDiff < -15) dayDiff += 31;
5856
+ const offsetMinutes = dayDiff * 24 * 60 + (tz.hour - utc.hour) * 60 + (tz.minute - utc.minute);
5857
+ return offsetMinutes;
5858
+ };
5859
+ var createDateInTimezone = (hours, minutes, timezone) => {
5860
+ const now = /* @__PURE__ */ new Date();
5861
+ try {
5862
+ const testFormatter = new Intl.DateTimeFormat("en-US", {
5863
+ timeZone: timezone
5864
+ });
5865
+ testFormatter.format(now);
5866
+ const offsetMinutes = getTimezoneOffsetMinutes(timezone, now);
5867
+ const tzFormatter = new Intl.DateTimeFormat("en-US", {
5868
+ timeZone: timezone,
5869
+ year: "numeric",
5870
+ month: "2-digit",
5871
+ day: "2-digit"
5872
+ });
5873
+ const parts = tzFormatter.formatToParts(now);
5874
+ const year = parts.find((p) => p.type === "year")?.value;
5875
+ const month = parts.find((p) => p.type === "month")?.value;
5876
+ const day = parts.find((p) => p.type === "day")?.value;
5877
+ if (!year || !month || !day) {
5878
+ return null;
5879
+ }
5880
+ const totalTargetMinutes = hours * 60 + minutes;
5881
+ const totalUtcMinutes = totalTargetMinutes - offsetMinutes;
5882
+ const baseDate = /* @__PURE__ */ new Date(`${year}-${month}-${day}T00:00:00.000Z`);
5883
+ const resetDate = new Date(
5884
+ baseDate.getTime() + totalUtcMinutes * 60 * 1e3
5885
+ );
5886
+ if (resetDate.getTime() <= now.getTime()) {
5887
+ resetDate.setTime(resetDate.getTime() + 24 * 60 * 60 * 1e3);
5888
+ }
5889
+ return resetDate;
5890
+ } catch {
5891
+ return null;
5892
+ }
5893
+ };
5894
+ var getFallbackTime = () => {
5895
+ const fallback = /* @__PURE__ */ new Date();
5896
+ fallback.setMinutes(fallback.getMinutes() + 30);
5897
+ return fallback.toISOString();
5898
+ };
5899
+
5900
+ // src/server/core/rate-limit/functions/readLastLine.ts
5901
+ import { FileSystem as FileSystem13 } from "@effect/platform";
5902
+ import { Effect as Effect39 } from "effect";
5903
+ var extractLastNonEmptyLine = (content) => {
5904
+ const lines = content.split(/\r?\n/);
5905
+ for (let i = lines.length - 1; i >= 0; i--) {
5906
+ const line = lines[i];
5907
+ if (line !== void 0 && line.trim() !== "") {
5908
+ return line;
5909
+ }
5910
+ }
5911
+ return "";
5912
+ };
5913
+ var readLastLine = (filePath) => Effect39.gen(function* () {
5914
+ const fs = yield* FileSystem13.FileSystem;
5915
+ const content = yield* fs.readFileString(filePath);
5916
+ return extractLastNonEmptyLine(content);
5917
+ });
5918
+
5919
+ // src/server/core/rate-limit/services/RateLimitAutoScheduleService.ts
5920
+ var LayerImpl25 = Effect40.gen(function* () {
5921
+ const eventBus = yield* EventBus;
5922
+ const userConfigService = yield* UserConfigService;
5923
+ const sessionProcessService = yield* ClaudeCodeSessionProcessService;
5924
+ const schedulerService = yield* SchedulerService;
5925
+ const fs = yield* FileSystem14.FileSystem;
5926
+ const pathService = yield* Path17.Path;
5927
+ const schedulerConfigBaseDir = yield* SchedulerConfigBaseDir;
5928
+ const projectRepository = yield* ProjectRepository;
5929
+ const lifeCycleService = yield* ClaudeCodeLifeCycleService;
5930
+ const listenerRef = yield* Ref12.make(null);
5931
+ const getSessionProcessProjectId = (sessionId) => Effect40.gen(function* () {
5932
+ const processes = yield* sessionProcessService.getSessionProcesses();
5933
+ const liveProcess = processes.find(
5934
+ (process2) => process2.sessionId === sessionId && (process2.type === "initialized" || process2.type === "file_created" || process2.type === "paused")
5935
+ );
5936
+ return liveProcess?.def.projectId;
5937
+ });
5938
+ const hasExistingReservedJobForSession = (sessionId) => Effect40.gen(function* () {
5939
+ const jobs = yield* schedulerService.getJobs().pipe(Effect40.catchAll(() => Effect40.succeed([])));
5940
+ return jobs.some(
5941
+ (job) => job.schedule.type === "reserved" && job.message.baseSessionId === sessionId && job.lastRunStatus === null
5942
+ // Not yet executed
5943
+ );
5944
+ });
5945
+ const handleSessionChanged = (event) => Effect40.gen(function* () {
5946
+ const { projectId, sessionId } = event;
5947
+ const config = yield* userConfigService.getUserConfig();
5948
+ if (!config.autoScheduleContinueOnRateLimit) {
5949
+ return;
5950
+ }
5951
+ const processProjectId = yield* getSessionProcessProjectId(sessionId);
5952
+ if (processProjectId === void 0) {
5953
+ return;
5954
+ }
5955
+ const hasExistingJob = yield* hasExistingReservedJobForSession(sessionId);
5956
+ if (hasExistingJob) {
5957
+ return;
5958
+ }
5959
+ const projectPath = decodeProjectId(projectId);
5960
+ const sessionFilePath = pathService.join(
5961
+ projectPath,
5962
+ `${sessionId}.jsonl`
5963
+ );
5964
+ const lastLine = yield* readLastLine(sessionFilePath).pipe(
5965
+ Effect40.catchAll(() => Effect40.succeed(""))
5966
+ );
5967
+ if (lastLine === "") {
5968
+ return;
5969
+ }
5970
+ const detection = detectRateLimitFromLastLine(lastLine);
5971
+ if (!detection.detected) {
5972
+ return;
5973
+ }
5974
+ const resetTime = parseRateLimitResetTime(detection.resetTimeText);
5975
+ yield* schedulerService.addJob({
5976
+ name: `Rate limit auto-continue: ${sessionId.slice(0, 8)}...`,
5977
+ schedule: {
5978
+ type: "reserved",
5979
+ reservedExecutionTime: resetTime
5980
+ },
5981
+ message: {
5982
+ content: "continue",
5983
+ projectId: processProjectId,
5984
+ baseSessionId: sessionId
5985
+ },
5986
+ enabled: true
5987
+ }).pipe(
5988
+ Effect40.catchAll((error) => {
5989
+ console.error(
5990
+ `[RateLimitAutoScheduleService] Failed to add job for session ${sessionId}:`,
5991
+ error
5992
+ );
5993
+ return Effect40.void;
5994
+ })
5995
+ );
5996
+ console.log(
5997
+ `[RateLimitAutoScheduleService] Scheduled continue task for session ${sessionId} at ${resetTime}`
5998
+ );
5999
+ });
6000
+ const runtimeLayer = Layer33.mergeAll(
6001
+ Layer33.succeed(FileSystem14.FileSystem, fs),
6002
+ Layer33.succeed(Path17.Path, pathService),
6003
+ Layer33.succeed(SchedulerConfigBaseDir, schedulerConfigBaseDir),
6004
+ Layer33.succeed(ProjectRepository, projectRepository),
6005
+ Layer33.succeed(UserConfigService, userConfigService),
6006
+ Layer33.succeed(ClaudeCodeLifeCycleService, lifeCycleService)
6007
+ );
6008
+ const start = () => Effect40.gen(function* () {
6009
+ const existingListener = yield* Ref12.get(listenerRef);
6010
+ if (existingListener !== null) {
6011
+ return;
6012
+ }
6013
+ const listener = (event) => {
6014
+ Effect40.runFork(
6015
+ handleSessionChanged(event).pipe(Effect40.provide(runtimeLayer))
6016
+ );
6017
+ };
6018
+ yield* Ref12.set(listenerRef, listener);
6019
+ yield* eventBus.on("sessionChanged", listener);
6020
+ console.log("[RateLimitAutoScheduleService] Started");
6021
+ });
6022
+ const stop = () => Effect40.gen(function* () {
6023
+ const listener = yield* Ref12.get(listenerRef);
6024
+ if (listener !== null) {
6025
+ yield* eventBus.off("sessionChanged", listener);
6026
+ yield* Ref12.set(listenerRef, null);
6027
+ }
6028
+ console.log("[RateLimitAutoScheduleService] Stopped");
6029
+ });
6030
+ return {
6031
+ start,
6032
+ stop
6033
+ };
6034
+ });
6035
+ var RateLimitAutoScheduleService = class extends Context31.Tag(
6036
+ "RateLimitAutoScheduleService"
6037
+ )() {
6038
+ static {
6039
+ this.Live = Layer33.effect(this, LayerImpl25);
6040
+ }
6041
+ };
6042
+
5678
6043
  // src/server/core/scheduler/presentation/SchedulerController.ts
5679
- import { Context as Context31, Effect as Effect39, Layer as Layer33 } from "effect";
5680
- var LayerImpl25 = Effect39.gen(function* () {
6044
+ import { Context as Context32, Effect as Effect41, Layer as Layer34 } from "effect";
6045
+ var LayerImpl26 = Effect41.gen(function* () {
5681
6046
  const schedulerService = yield* SchedulerService;
5682
- const getJobs = () => Effect39.gen(function* () {
6047
+ const getJobs = () => Effect41.gen(function* () {
5683
6048
  const jobs = yield* schedulerService.getJobs();
5684
6049
  return {
5685
6050
  response: jobs,
5686
6051
  status: 200
5687
6052
  };
5688
6053
  });
5689
- const addJob = (options) => Effect39.gen(function* () {
6054
+ const addJob = (options) => Effect41.gen(function* () {
5690
6055
  const { job } = options;
5691
6056
  const result = yield* schedulerService.addJob(job);
5692
6057
  return {
@@ -5694,12 +6059,12 @@ var LayerImpl25 = Effect39.gen(function* () {
5694
6059
  status: 201
5695
6060
  };
5696
6061
  });
5697
- const updateJob = (options) => Effect39.gen(function* () {
6062
+ const updateJob = (options) => Effect41.gen(function* () {
5698
6063
  const { id, job } = options;
5699
6064
  const result = yield* schedulerService.updateJob(id, job).pipe(
5700
- Effect39.catchTag(
6065
+ Effect41.catchTag(
5701
6066
  "SchedulerJobNotFoundError",
5702
- () => Effect39.succeed(null)
6067
+ () => Effect41.succeed(null)
5703
6068
  )
5704
6069
  );
5705
6070
  if (result === null) {
@@ -5713,14 +6078,14 @@ var LayerImpl25 = Effect39.gen(function* () {
5713
6078
  status: 200
5714
6079
  };
5715
6080
  });
5716
- const deleteJob = (options) => Effect39.gen(function* () {
6081
+ const deleteJob = (options) => Effect41.gen(function* () {
5717
6082
  const { id } = options;
5718
6083
  const result = yield* schedulerService.deleteJob(id).pipe(
5719
- Effect39.catchTag(
6084
+ Effect41.catchTag(
5720
6085
  "SchedulerJobNotFoundError",
5721
- () => Effect39.succeed(false)
6086
+ () => Effect41.succeed(false)
5722
6087
  ),
5723
- Effect39.map(() => true)
6088
+ Effect41.map(() => true)
5724
6089
  );
5725
6090
  if (!result) {
5726
6091
  return {
@@ -5740,18 +6105,18 @@ var LayerImpl25 = Effect39.gen(function* () {
5740
6105
  deleteJob
5741
6106
  };
5742
6107
  });
5743
- var SchedulerController = class extends Context31.Tag("SchedulerController")() {
6108
+ var SchedulerController = class extends Context32.Tag("SchedulerController")() {
5744
6109
  static {
5745
- this.Live = Layer33.effect(this, LayerImpl25);
6110
+ this.Live = Layer34.effect(this, LayerImpl26);
5746
6111
  }
5747
6112
  };
5748
6113
 
5749
6114
  // src/server/core/search/presentation/SearchController.ts
5750
- import { Context as Context33, Effect as Effect41, Layer as Layer35 } from "effect";
6115
+ import { Context as Context34, Effect as Effect43, Layer as Layer36 } from "effect";
5751
6116
 
5752
6117
  // src/server/core/search/services/SearchService.ts
5753
- import { FileSystem as FileSystem13, Path as Path17 } from "@effect/platform";
5754
- import { Context as Context32, Effect as Effect40, Layer as Layer34, Ref as Ref12 } from "effect";
6118
+ import { FileSystem as FileSystem15, Path as Path18 } from "@effect/platform";
6119
+ import { Context as Context33, Effect as Effect42, Layer as Layer35, Ref as Ref13 } from "effect";
5755
6120
  import MiniSearch from "minisearch";
5756
6121
 
5757
6122
  // src/server/core/search/functions/extractSearchableText.ts
@@ -5797,12 +6162,12 @@ var createMiniSearchIndex = () => new MiniSearch({
5797
6162
  boost: { text: 1 }
5798
6163
  }
5799
6164
  });
5800
- var LayerImpl26 = Effect40.gen(function* () {
5801
- const fs = yield* FileSystem13.FileSystem;
5802
- const path = yield* Path17.Path;
6165
+ var LayerImpl27 = Effect42.gen(function* () {
6166
+ const fs = yield* FileSystem15.FileSystem;
6167
+ const path = yield* Path18.Path;
5803
6168
  const context = yield* ApplicationContext;
5804
- const indexCacheRef = yield* Ref12.make(null);
5805
- const buildIndex = () => Effect40.gen(function* () {
6169
+ const indexCacheRef = yield* Ref13.make(null);
6170
+ const buildIndex = () => Effect42.gen(function* () {
5806
6171
  const { claudeProjectsDirPath } = yield* context.claudeCodePaths;
5807
6172
  const dirExists = yield* fs.exists(claudeProjectsDirPath);
5808
6173
  if (!dirExists) {
@@ -5811,22 +6176,22 @@ var LayerImpl26 = Effect40.gen(function* () {
5811
6176
  const projectEntries = yield* fs.readDirectory(claudeProjectsDirPath);
5812
6177
  const miniSearch = createMiniSearchIndex();
5813
6178
  const documentEffects = projectEntries.map(
5814
- (projectEntry) => Effect40.gen(function* () {
6179
+ (projectEntry) => Effect42.gen(function* () {
5815
6180
  const projectPath = path.resolve(claudeProjectsDirPath, projectEntry);
5816
- const stat = yield* fs.stat(projectPath).pipe(Effect40.catchAll(() => Effect40.succeed(null)));
6181
+ const stat = yield* fs.stat(projectPath).pipe(Effect42.catchAll(() => Effect42.succeed(null)));
5817
6182
  if (stat?.type !== "Directory") {
5818
6183
  return [];
5819
6184
  }
5820
6185
  const projectId = encodeProjectId(projectPath);
5821
6186
  const projectName = path.basename(projectPath);
5822
- const sessionEntries = yield* fs.readDirectory(projectPath).pipe(Effect40.catchAll(() => Effect40.succeed([])));
6187
+ const sessionEntries = yield* fs.readDirectory(projectPath).pipe(Effect42.catchAll(() => Effect42.succeed([])));
5823
6188
  const sessionFiles = sessionEntries.filter(isRegularSessionFile);
5824
- const sessionDocuments = yield* Effect40.all(
6189
+ const sessionDocuments = yield* Effect42.all(
5825
6190
  sessionFiles.map(
5826
- (sessionFile) => Effect40.gen(function* () {
6191
+ (sessionFile) => Effect42.gen(function* () {
5827
6192
  const sessionPath = path.resolve(projectPath, sessionFile);
5828
6193
  const sessionId = encodeSessionId(sessionPath);
5829
- const content = yield* fs.readFileString(sessionPath).pipe(Effect40.catchAll(() => Effect40.succeed("")));
6194
+ const content = yield* fs.readFileString(sessionPath).pipe(Effect42.catchAll(() => Effect42.succeed("")));
5830
6195
  if (!content) return [];
5831
6196
  const conversations = parseJsonl(content);
5832
6197
  const documents = [];
@@ -5861,7 +6226,7 @@ var LayerImpl26 = Effect40.gen(function* () {
5861
6226
  return sessionDocuments.flat();
5862
6227
  })
5863
6228
  );
5864
- const allDocuments = yield* Effect40.all(documentEffects, {
6229
+ const allDocuments = yield* Effect42.all(documentEffects, {
5865
6230
  concurrency: 10
5866
6231
  });
5867
6232
  const flatDocuments = allDocuments.flat();
@@ -5872,17 +6237,17 @@ var LayerImpl26 = Effect40.gen(function* () {
5872
6237
  }
5873
6238
  return { index: miniSearch, documents: documentsMap };
5874
6239
  });
5875
- const getIndex = () => Effect40.gen(function* () {
5876
- const cached = yield* Ref12.get(indexCacheRef);
6240
+ const getIndex = () => Effect42.gen(function* () {
6241
+ const cached = yield* Ref13.get(indexCacheRef);
5877
6242
  const now = Date.now();
5878
6243
  if (cached && now - cached.builtAt < INDEX_TTL_MS) {
5879
6244
  return { index: cached.index, documents: cached.documents };
5880
6245
  }
5881
6246
  const { index, documents } = yield* buildIndex();
5882
- yield* Ref12.set(indexCacheRef, { index, documents, builtAt: now });
6247
+ yield* Ref13.set(indexCacheRef, { index, documents, builtAt: now });
5883
6248
  return { index, documents };
5884
6249
  });
5885
- const search = (query4, limit = 20, projectId) => Effect40.gen(function* () {
6250
+ const search = (query4, limit = 20, projectId) => Effect42.gen(function* () {
5886
6251
  const { claudeProjectsDirPath } = yield* context.claudeCodePaths;
5887
6252
  const dirExists = yield* fs.exists(claudeProjectsDirPath);
5888
6253
  if (!dirExists) {
@@ -5923,22 +6288,22 @@ var LayerImpl26 = Effect40.gen(function* () {
5923
6288
  }
5924
6289
  return { results };
5925
6290
  });
5926
- const invalidateIndex = () => Ref12.set(indexCacheRef, null);
6291
+ const invalidateIndex = () => Ref13.set(indexCacheRef, null);
5927
6292
  return {
5928
6293
  search,
5929
6294
  invalidateIndex
5930
6295
  };
5931
6296
  });
5932
- var SearchService = class extends Context32.Tag("SearchService")() {
6297
+ var SearchService = class extends Context33.Tag("SearchService")() {
5933
6298
  static {
5934
- this.Live = Layer34.effect(this, LayerImpl26);
6299
+ this.Live = Layer35.effect(this, LayerImpl27);
5935
6300
  }
5936
6301
  };
5937
6302
 
5938
6303
  // src/server/core/search/presentation/SearchController.ts
5939
- var LayerImpl27 = Effect41.gen(function* () {
6304
+ var LayerImpl28 = Effect43.gen(function* () {
5940
6305
  const searchService = yield* SearchService;
5941
- const search = (options) => Effect41.gen(function* () {
6306
+ const search = (options) => Effect43.gen(function* () {
5942
6307
  const { query: query4, limit, projectId } = options;
5943
6308
  if (query4.trim().length < 2) {
5944
6309
  return {
@@ -5962,18 +6327,18 @@ var LayerImpl27 = Effect41.gen(function* () {
5962
6327
  search
5963
6328
  };
5964
6329
  });
5965
- var SearchController = class extends Context33.Tag("SearchController")() {
6330
+ var SearchController = class extends Context34.Tag("SearchController")() {
5966
6331
  static {
5967
- this.Live = Layer35.effect(this, LayerImpl27);
6332
+ this.Live = Layer36.effect(this, LayerImpl28);
5968
6333
  }
5969
6334
  };
5970
6335
 
5971
6336
  // src/server/core/session/presentation/SessionController.ts
5972
- import { FileSystem as FileSystem14 } from "@effect/platform";
5973
- import { Context as Context34, Effect as Effect43, Layer as Layer36 } from "effect";
6337
+ import { FileSystem as FileSystem16 } from "@effect/platform";
6338
+ import { Context as Context35, Effect as Effect45, Layer as Layer37 } from "effect";
5974
6339
 
5975
6340
  // src/server/core/session/services/ExportService.ts
5976
- import { Effect as Effect42 } from "effect";
6341
+ import { Effect as Effect44 } from "effect";
5977
6342
  var escapeHtml = (text) => {
5978
6343
  const map = {
5979
6344
  "&": "&amp;",
@@ -6000,33 +6365,166 @@ var formatTimestamp = (timestamp) => {
6000
6365
  });
6001
6366
  };
6002
6367
  var renderMarkdown = (content) => {
6003
- let html = escapeHtml(content);
6004
- html = html.replace(
6368
+ const codeBlocks = [];
6369
+ let processedContent = content.replace(
6005
6370
  /```(\w+)?\n([\s\S]*?)```/g,
6006
- (_match, lang, code) => `
6371
+ (_match, lang, code) => {
6372
+ const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`;
6373
+ codeBlocks.push(`
6007
6374
  <div class="code-block">
6008
6375
  ${lang ? `<div class="code-header"><span class="code-lang">${escapeHtml(lang.toUpperCase())}</span></div>` : ""}
6009
- <pre><code class="language-${escapeHtml(lang || "text")}">${code.trim()}</code></pre>
6376
+ <pre><code class="language-${escapeHtml(lang || "text")}">${escapeHtml(code.trim())}</code></pre>
6010
6377
  </div>
6011
- `
6378
+ `);
6379
+ return placeholder;
6380
+ }
6381
+ );
6382
+ processedContent = processedContent.replace(
6383
+ /(?:^\|.+\|$\n?)+/gm,
6384
+ (tableBlock) => {
6385
+ const rows = tableBlock.trim().split("\n");
6386
+ if (rows.length < 2) return escapeHtml(tableBlock);
6387
+ const headerRow = rows[0];
6388
+ const separatorRow = rows[1];
6389
+ if (!headerRow || !separatorRow || !/^\|[\s\-:|]+\|$/.test(separatorRow)) {
6390
+ return escapeHtml(tableBlock);
6391
+ }
6392
+ const parseRow = (row) => row.split("|").slice(1, -1).map((cell) => cell.trim());
6393
+ const headerCells = parseRow(headerRow);
6394
+ const dataRows = rows.slice(2);
6395
+ let tableHtml = '<table class="markdown-table"><thead><tr>';
6396
+ for (const cell of headerCells) {
6397
+ tableHtml += `<th>${escapeHtml(cell)}</th>`;
6398
+ }
6399
+ tableHtml += "</tr></thead><tbody>";
6400
+ for (const row of dataRows) {
6401
+ const cells = parseRow(row);
6402
+ tableHtml += "<tr>";
6403
+ for (const cell of cells) {
6404
+ tableHtml += `<td>${escapeHtml(cell)}</td>`;
6405
+ }
6406
+ tableHtml += "</tr>";
6407
+ }
6408
+ tableHtml += "</tbody></table>";
6409
+ return tableHtml;
6410
+ }
6411
+ );
6412
+ processedContent = processedContent.split(
6413
+ /(<table class="markdown-table">[\s\S]*?<\/table>|__CODE_BLOCK_\d+__)/
6414
+ ).map((part) => {
6415
+ if (part.startsWith('<table class="markdown-table">') || /^__CODE_BLOCK_\d+__$/.test(part)) {
6416
+ return part;
6417
+ }
6418
+ return escapeHtml(part);
6419
+ }).join("");
6420
+ processedContent = processedContent.replace(
6421
+ /(?:^&gt; .+$\n?)+/gm,
6422
+ (quoteBlock) => {
6423
+ const lines = quoteBlock.split("\n").filter((l) => l.trim()).map((l) => l.replace(/^&gt; /, "")).join("<br>");
6424
+ return `<blockquote class="markdown-blockquote">${lines}</blockquote>`;
6425
+ }
6426
+ );
6427
+ processedContent = processedContent.replace(
6428
+ /^(\*{3,}|-{3,}|_{3,})$/gm,
6429
+ '<hr class="markdown-hr">'
6430
+ );
6431
+ processedContent = processedContent.replace(
6432
+ /(?:^- \[([ xX])\] .+$\n?)+/gm,
6433
+ (listBlock) => {
6434
+ const items = listBlock.trim().split("\n").map((line) => {
6435
+ const match = line.match(/^- \[([ xX])\] (.+)$/);
6436
+ if (match?.[1] !== void 0 && match[2] !== void 0) {
6437
+ const checked = match[1].toLowerCase() === "x";
6438
+ return `<li class="task-item"><input type="checkbox" class="task-checkbox" ${checked ? "checked" : ""} disabled>${match[2]}</li>`;
6439
+ }
6440
+ return "";
6441
+ }).join("");
6442
+ return `<ul class="markdown-task-list">${items}</ul>`;
6443
+ }
6444
+ );
6445
+ processedContent = processedContent.replace(
6446
+ /(?:^[-*+] .+$\n?)+/gm,
6447
+ (listBlock) => {
6448
+ const items = listBlock.trim().split("\n").map((line) => {
6449
+ const match = line.match(/^[-*+] (.+)$/);
6450
+ return match ? `<li>${match[1]}</li>` : "";
6451
+ }).join("");
6452
+ return `<ul class="markdown-ul">${items}</ul>`;
6453
+ }
6454
+ );
6455
+ processedContent = processedContent.replace(
6456
+ /(?:^\d+\. .+$\n?)+/gm,
6457
+ (listBlock) => {
6458
+ const items = listBlock.trim().split("\n").map((line) => {
6459
+ const match = line.match(/^\d+\. (.+)$/);
6460
+ return match ? `<li>${match[1]}</li>` : "";
6461
+ }).join("");
6462
+ return `<ol class="markdown-ol">${items}</ol>`;
6463
+ }
6464
+ );
6465
+ processedContent = processedContent.replace(
6466
+ /~~(.+?)~~/g,
6467
+ '<del class="markdown-del">$1</del>'
6468
+ );
6469
+ processedContent = processedContent.replace(
6470
+ /`([^`]+)`/g,
6471
+ '<code class="inline-code">$1</code>'
6012
6472
  );
6013
- html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
6014
- html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
6015
- html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
6016
- html = html.replace(/^### (.+)$/gm, '<h3 class="markdown-h3">$1</h3>');
6017
- html = html.replace(/^## (.+)$/gm, '<h2 class="markdown-h2">$1</h2>');
6018
- html = html.replace(/^# (.+)$/gm, '<h1 class="markdown-h1">$1</h1>');
6019
- html = html.replace(
6473
+ processedContent = processedContent.replace(
6474
+ /\*\*(.+?)\*\*/g,
6475
+ "<strong>$1</strong>"
6476
+ );
6477
+ processedContent = processedContent.replace(
6478
+ /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g,
6479
+ "<em>$1</em>"
6480
+ );
6481
+ processedContent = processedContent.replace(
6482
+ /^### (.+)$/gm,
6483
+ '<h3 class="markdown-h3">$1</h3>'
6484
+ );
6485
+ processedContent = processedContent.replace(
6486
+ /^## (.+)$/gm,
6487
+ '<h2 class="markdown-h2">$1</h2>'
6488
+ );
6489
+ processedContent = processedContent.replace(
6490
+ /^# (.+)$/gm,
6491
+ '<h1 class="markdown-h1">$1</h1>'
6492
+ );
6493
+ processedContent = processedContent.replace(
6020
6494
  /\[([^\]]+)\]\(([^)]+)\)/g,
6021
6495
  '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
6022
6496
  );
6023
- html = html.split("\n\n").map((para) => {
6024
- if (para.startsWith("<h") || para.startsWith("<div") || para.startsWith("<pre") || para.trim() === "") {
6497
+ const blockElements = [
6498
+ "<h1",
6499
+ "<h2",
6500
+ "<h3",
6501
+ "<div",
6502
+ "<pre",
6503
+ "<table",
6504
+ "<ul",
6505
+ "<ol",
6506
+ "<blockquote",
6507
+ "<hr",
6508
+ "__CODE_BLOCK_"
6509
+ ];
6510
+ processedContent = processedContent.split("\n\n").map((para) => {
6511
+ const trimmed = para.trim();
6512
+ if (trimmed === "") return "";
6513
+ if (blockElements.some((tag) => trimmed.startsWith(tag))) {
6025
6514
  return para;
6026
6515
  }
6027
6516
  return `<p class="markdown-p">${para.replace(/\n/g, "<br>")}</p>`;
6028
- }).join("\n");
6029
- return html;
6517
+ }).filter((p) => p !== "").join("\n");
6518
+ for (let i = 0; i < codeBlocks.length; i++) {
6519
+ const codeBlock = codeBlocks[i];
6520
+ if (codeBlock !== void 0) {
6521
+ processedContent = processedContent.replace(
6522
+ `__CODE_BLOCK_${i}__`,
6523
+ codeBlock
6524
+ );
6525
+ }
6526
+ }
6527
+ return processedContent;
6030
6528
  };
6031
6529
  var renderUserEntry = (entry) => {
6032
6530
  const contentArray = Array.isArray(entry.message.content) ? entry.message.content : [entry.message.content];
@@ -6063,7 +6561,289 @@ var renderUserEntry = (entry) => {
6063
6561
  </div>
6064
6562
  `;
6065
6563
  };
6066
- var renderAssistantEntry = (entry) => {
6564
+ var renderToolResultContent = (result) => {
6565
+ const isError = result.is_error === true;
6566
+ const errorClass = isError ? " tool-result-error" : "";
6567
+ let contentHtml;
6568
+ if (typeof result.content === "string") {
6569
+ contentHtml = `<pre class="tool-result-text">${escapeHtml(result.content)}</pre>`;
6570
+ } else {
6571
+ contentHtml = result.content.map((item) => {
6572
+ if (item.type === "text") {
6573
+ return `<pre class="tool-result-text">${escapeHtml(item.text)}</pre>`;
6574
+ }
6575
+ if (item.type === "image") {
6576
+ return `<img src="data:${item.source.media_type};base64,${item.source.data}" alt="Tool result image" class="tool-result-image" />`;
6577
+ }
6578
+ return "";
6579
+ }).join("");
6580
+ }
6581
+ return `
6582
+ <div class="tool-result-block${errorClass}">
6583
+ <div class="tool-result-header">
6584
+ <svg class="icon-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6585
+ ${isError ? '<path d="M18 6L6 18M6 6l12 12"/>' : '<path d="M20 6L9 17l-5-5"/>'}
6586
+ </svg>
6587
+ <span class="tool-result-label">${isError ? "Error" : "Result"}</span>
6588
+ </div>
6589
+ <div class="tool-result-content">
6590
+ ${contentHtml}
6591
+ </div>
6592
+ </div>
6593
+ `;
6594
+ };
6595
+ var hasAgentId = (toolUseResult) => {
6596
+ return typeof toolUseResult === "object" && toolUseResult !== null && "agentId" in toolUseResult && typeof toolUseResult.agentId === "string";
6597
+ };
6598
+ var buildSidechainData = (conversations) => {
6599
+ const sidechainConversations = conversations.filter(
6600
+ (conv) => conv.type !== "summary" && conv.type !== "file-history-snapshot" && conv.type !== "queue-operation" && conv.type !== "progress" && conv.isSidechain === true
6601
+ );
6602
+ const uuidMap = new Map(
6603
+ sidechainConversations.map((conv) => [conv.uuid, conv])
6604
+ );
6605
+ const getRootConversation = (conv) => {
6606
+ if (conv.parentUuid === null) {
6607
+ return conv;
6608
+ }
6609
+ const parent = uuidMap.get(conv.parentUuid);
6610
+ if (parent === void 0) {
6611
+ return conv;
6612
+ }
6613
+ return getRootConversation(parent);
6614
+ };
6615
+ const groupsByRootUuid = /* @__PURE__ */ new Map();
6616
+ for (const conv of sidechainConversations) {
6617
+ const root = getRootConversation(conv);
6618
+ const existing = groupsByRootUuid.get(root.uuid);
6619
+ if (existing) {
6620
+ existing.push(conv);
6621
+ } else {
6622
+ groupsByRootUuid.set(root.uuid, [conv]);
6623
+ }
6624
+ }
6625
+ for (const [, convs] of groupsByRootUuid) {
6626
+ convs.sort(
6627
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
6628
+ );
6629
+ }
6630
+ const promptToRoot = /* @__PURE__ */ new Map();
6631
+ for (const conv of sidechainConversations) {
6632
+ if (conv.type === "user" && conv.parentUuid === null && typeof conv.message.content === "string") {
6633
+ promptToRoot.set(conv.message.content, conv);
6634
+ }
6635
+ }
6636
+ const agentIdToRoot = /* @__PURE__ */ new Map();
6637
+ for (const conv of sidechainConversations) {
6638
+ if (conv.parentUuid === null && conv.agentId !== void 0) {
6639
+ agentIdToRoot.set(conv.agentId, conv);
6640
+ }
6641
+ }
6642
+ const toolUseIdToAgentId = /* @__PURE__ */ new Map();
6643
+ for (const conv of conversations) {
6644
+ if (conv.type === "summary" || conv.type === "file-history-snapshot" || conv.type === "queue-operation" || conv.type === "progress") {
6645
+ continue;
6646
+ }
6647
+ if (conv.type !== "user") continue;
6648
+ const messageContent = conv.message.content;
6649
+ if (typeof messageContent === "string") continue;
6650
+ for (const content of messageContent) {
6651
+ if (typeof content === "string") continue;
6652
+ if (content.type === "tool_result") {
6653
+ const toolUseResult = conv.toolUseResult;
6654
+ if (hasAgentId(toolUseResult)) {
6655
+ toolUseIdToAgentId.set(content.tool_use_id, toolUseResult.agentId);
6656
+ }
6657
+ }
6658
+ }
6659
+ }
6660
+ return { groupsByRootUuid, promptToRoot, agentIdToRoot, toolUseIdToAgentId };
6661
+ };
6662
+ var renderSidechainEntry = (entry, toolResultMap, sidechainData) => {
6663
+ if (entry.type === "user") {
6664
+ const contentArray = Array.isArray(entry.message.content) ? entry.message.content : [entry.message.content];
6665
+ const contentHtml = contentArray.map((msg) => {
6666
+ if (typeof msg === "string") {
6667
+ return `<div class="markdown-content">${renderMarkdown(msg)}</div>`;
6668
+ }
6669
+ if (msg.type === "text") {
6670
+ return `<div class="markdown-content">${renderMarkdown(msg.text)}</div>`;
6671
+ }
6672
+ if (msg.type === "tool_result") {
6673
+ return "";
6674
+ }
6675
+ return "";
6676
+ }).join("");
6677
+ if (!contentHtml.trim()) return "";
6678
+ return `
6679
+ <div class="sidechain-entry sidechain-user-entry">
6680
+ <div class="sidechain-entry-header">
6681
+ <span class="sidechain-role">User</span>
6682
+ <span class="sidechain-timestamp">${formatTimestamp(entry.timestamp)}</span>
6683
+ </div>
6684
+ <div class="sidechain-entry-content">${contentHtml}</div>
6685
+ </div>
6686
+ `;
6687
+ }
6688
+ if (entry.type === "assistant") {
6689
+ const contentHtml = entry.message.content.map((msg) => {
6690
+ if (msg.type === "text") {
6691
+ return `<div class="markdown-content">${renderMarkdown(msg.text)}</div>`;
6692
+ }
6693
+ if (msg.type === "thinking") {
6694
+ const charCount = msg.thinking.length;
6695
+ return `
6696
+ <div class="thinking-block collapsible collapsed">
6697
+ <div class="thinking-header collapsible-trigger">
6698
+ <svg class="icon-lightbulb" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6699
+ <path d="M12 2v1m0 18v1m9-10h1M2 12H1m17.66-7.66l.71.71M3.63 20.37l.71.71m0-14.14l-.71.71m17.02 12.73l-.71.71M12 7a5 5 0 0 1 5 5 5 5 0 0 1-1.47 3.53c-.6.6-.94 1.42-.94 2.27V18a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-.2c0-.85-.34-1.67-.94-2.27A5 5 0 0 1 7 12a5 5 0 0 1 5-5Z"/>
6700
+ </svg>
6701
+ <span class="thinking-title">Thinking</span>
6702
+ <span class="expand-hint">(${charCount} chars)</span>
6703
+ <svg class="icon-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6704
+ <polyline points="6 9 12 15 18 9"></polyline>
6705
+ </svg>
6706
+ </div>
6707
+ <div class="thinking-content collapsible-content">
6708
+ <pre class="thinking-text">${escapeHtml(msg.thinking)}</pre>
6709
+ </div>
6710
+ </div>
6711
+ `;
6712
+ }
6713
+ if (msg.type === "tool_use") {
6714
+ const toolResult = toolResultMap.get(msg.id);
6715
+ if (msg.name === "Task") {
6716
+ return renderTaskTool(
6717
+ msg.id,
6718
+ msg.input,
6719
+ toolResult,
6720
+ sidechainData,
6721
+ toolResultMap
6722
+ );
6723
+ }
6724
+ const inputKeys = Object.keys(msg.input).length;
6725
+ const toolResultHtml = toolResult ? renderToolResultContent(toolResult) : "";
6726
+ return `
6727
+ <div class="tool-use-block collapsible collapsed">
6728
+ <div class="tool-use-header collapsible-trigger">
6729
+ <svg class="icon-wrench" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6730
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
6731
+ </svg>
6732
+ <span class="tool-name">${escapeHtml(msg.name)}</span>
6733
+ <span class="expand-hint">(${inputKeys} params)</span>
6734
+ <svg class="icon-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6735
+ <polyline points="6 9 12 15 18 9"></polyline>
6736
+ </svg>
6737
+ </div>
6738
+ <div class="tool-use-content collapsible-content">
6739
+ <div class="tool-id"><strong>Tool ID:</strong> <code>${escapeHtml(msg.id)}</code></div>
6740
+ <div class="tool-input">
6741
+ <strong>Input:</strong>
6742
+ <pre class="json-input">${escapeHtml(formatJsonWithNewlines(msg.input))}</pre>
6743
+ </div>
6744
+ ${toolResultHtml}
6745
+ </div>
6746
+ </div>
6747
+ `;
6748
+ }
6749
+ return "";
6750
+ }).join("");
6751
+ return `
6752
+ <div class="sidechain-entry sidechain-assistant-entry">
6753
+ <div class="sidechain-entry-header">
6754
+ <span class="sidechain-role">Subagent</span>
6755
+ <span class="sidechain-timestamp">${formatTimestamp(entry.timestamp)}</span>
6756
+ </div>
6757
+ <div class="sidechain-entry-content">${contentHtml}</div>
6758
+ </div>
6759
+ `;
6760
+ }
6761
+ if (entry.type === "system") {
6762
+ const content = "content" in entry && typeof entry.content === "string" ? entry.content : "System message";
6763
+ return `
6764
+ <div class="sidechain-entry sidechain-system-entry">
6765
+ <div class="sidechain-entry-header">
6766
+ <span class="sidechain-role">System</span>
6767
+ <span class="sidechain-timestamp">${formatTimestamp(entry.timestamp)}</span>
6768
+ </div>
6769
+ <div class="sidechain-entry-content">
6770
+ <div class="system-message">${escapeHtml(content)}</div>
6771
+ </div>
6772
+ </div>
6773
+ `;
6774
+ }
6775
+ return "";
6776
+ };
6777
+ var renderTaskTool = (toolId, input, toolResult, sidechainData, toolResultMap) => {
6778
+ const prompt = typeof input.prompt === "string" ? input.prompt : "";
6779
+ const truncatedPrompt = prompt.length > 200 ? `${prompt.slice(0, 200)}...` : prompt;
6780
+ let sidechainConversations = [];
6781
+ const agentId = sidechainData.toolUseIdToAgentId.get(toolId);
6782
+ if (agentId) {
6783
+ const rootByAgentId = sidechainData.agentIdToRoot.get(agentId);
6784
+ if (rootByAgentId) {
6785
+ const convs = sidechainData.groupsByRootUuid.get(rootByAgentId.uuid);
6786
+ if (convs) {
6787
+ sidechainConversations = convs;
6788
+ }
6789
+ }
6790
+ }
6791
+ if (sidechainConversations.length === 0) {
6792
+ const rootConversation = sidechainData.promptToRoot.get(prompt);
6793
+ if (rootConversation) {
6794
+ const convs = sidechainData.groupsByRootUuid.get(rootConversation.uuid);
6795
+ if (convs) {
6796
+ sidechainConversations = convs;
6797
+ }
6798
+ }
6799
+ }
6800
+ const hasSidechain = sidechainConversations.length > 0;
6801
+ const sidechainHtml = hasSidechain ? `
6802
+ <div class="sidechain-container collapsible">
6803
+ <div class="sidechain-header collapsible-trigger">
6804
+ <svg class="icon-layers" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6805
+ <polygon points="12 2 2 7 12 12 22 7 12 2"/>
6806
+ <polyline points="2 17 12 22 22 17"/>
6807
+ <polyline points="2 12 12 17 22 12"/>
6808
+ </svg>
6809
+ <span>Subagent Work Log (${sidechainConversations.length} entries)</span>
6810
+ <svg class="icon-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6811
+ <polyline points="6 9 12 15 18 9"></polyline>
6812
+ </svg>
6813
+ </div>
6814
+ <div class="sidechain-content collapsible-content">
6815
+ ${sidechainConversations.map(
6816
+ (conv) => renderSidechainEntry(conv, toolResultMap, sidechainData)
6817
+ ).filter((html) => html !== "").join("\n")}
6818
+ </div>
6819
+ </div>
6820
+ ` : "";
6821
+ return `
6822
+ <div class="task-tool-block collapsible">
6823
+ <div class="task-tool-header collapsible-trigger">
6824
+ <svg class="icon-task" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6825
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
6826
+ <path d="M9 12l2 2 4-4"/>
6827
+ </svg>
6828
+ <span class="task-tool-name">Task${hasSidechain ? ` (${sidechainConversations.length} steps)` : ""}</span>
6829
+ <span class="task-prompt-preview">${escapeHtml(truncatedPrompt)}</span>
6830
+ <svg class="icon-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
6831
+ <polyline points="6 9 12 15 18 9"></polyline>
6832
+ </svg>
6833
+ </div>
6834
+ <div class="task-tool-content collapsible-content">
6835
+ <div class="task-tool-id"><strong>Task ID:</strong> <code>${escapeHtml(toolId)}</code></div>
6836
+ <div class="task-prompt">
6837
+ <strong>Prompt:</strong>
6838
+ <div class="task-prompt-text">${renderMarkdown(prompt)}</div>
6839
+ </div>
6840
+ ${toolResult ? renderToolResultContent(toolResult) : ""}
6841
+ ${sidechainHtml}
6842
+ </div>
6843
+ </div>
6844
+ `;
6845
+ };
6846
+ var renderAssistantEntry = (entry, toolResultMap, sidechainData) => {
6067
6847
  const contentHtml = entry.message.content.map((msg) => {
6068
6848
  if (msg.type === "text") {
6069
6849
  return `<div class="markdown-content">${renderMarkdown(msg.text)}</div>`;
@@ -6089,7 +6869,18 @@ var renderAssistantEntry = (entry) => {
6089
6869
  `;
6090
6870
  }
6091
6871
  if (msg.type === "tool_use") {
6872
+ const toolResult = toolResultMap.get(msg.id);
6873
+ if (msg.name === "Task") {
6874
+ return renderTaskTool(
6875
+ msg.id,
6876
+ msg.input,
6877
+ toolResult,
6878
+ sidechainData,
6879
+ toolResultMap
6880
+ );
6881
+ }
6092
6882
  const inputKeys = Object.keys(msg.input).length;
6883
+ const toolResultHtml = toolResult ? renderToolResultContent(toolResult) : "";
6093
6884
  return `
6094
6885
  <div class="tool-use-block collapsible">
6095
6886
  <div class="tool-use-header collapsible-trigger">
@@ -6108,6 +6899,7 @@ var renderAssistantEntry = (entry) => {
6108
6899
  <strong>Input Parameters:</strong>
6109
6900
  <pre class="json-input">${escapeHtml(formatJsonWithNewlines(msg.input))}</pre>
6110
6901
  </div>
6902
+ ${toolResultHtml}
6111
6903
  </div>
6112
6904
  </div>
6113
6905
  `;
@@ -6175,7 +6967,7 @@ var groupConsecutiveAssistantMessages = (conversations) => {
6175
6967
  }
6176
6968
  return grouped;
6177
6969
  };
6178
- var renderGroupedAssistantEntries = (entries) => {
6970
+ var renderGroupedAssistantEntries = (entries, toolResultMap, sidechainData) => {
6179
6971
  const allContent = entries.flatMap((entry) => entry.message.content);
6180
6972
  const firstEntry = entries[0];
6181
6973
  if (!firstEntry) {
@@ -6206,7 +6998,18 @@ var renderGroupedAssistantEntries = (entries) => {
6206
6998
  `;
6207
6999
  }
6208
7000
  if (msg.type === "tool_use") {
7001
+ const toolResult = toolResultMap.get(msg.id);
7002
+ if (msg.name === "Task") {
7003
+ return renderTaskTool(
7004
+ msg.id,
7005
+ msg.input,
7006
+ toolResult,
7007
+ sidechainData,
7008
+ toolResultMap
7009
+ );
7010
+ }
6209
7011
  const inputKeys = Object.keys(msg.input).length;
7012
+ const toolResultHtml = toolResult ? renderToolResultContent(toolResult) : "";
6210
7013
  return `
6211
7014
  <div class="tool-use-block collapsible">
6212
7015
  <div class="tool-use-header collapsible-trigger">
@@ -6225,6 +7028,7 @@ var renderGroupedAssistantEntries = (entries) => {
6225
7028
  <strong>Input Parameters:</strong>
6226
7029
  <pre class="json-input">${escapeHtml(formatJsonWithNewlines(msg.input))}</pre>
6227
7030
  </div>
7031
+ ${toolResultHtml}
6228
7032
  </div>
6229
7033
  </div>
6230
7034
  `;
@@ -6243,12 +7047,88 @@ var renderGroupedAssistantEntries = (entries) => {
6243
7047
  </div>
6244
7048
  `;
6245
7049
  };
6246
- var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
7050
+ var generateSessionHtml = (session, projectId, agentSessionRepo) => Effect44.gen(function* () {
7051
+ const agentIds = /* @__PURE__ */ new Set();
7052
+ for (const conv of session.conversations) {
7053
+ if (conv.type !== "user" || typeof conv.message.content === "string") {
7054
+ continue;
7055
+ }
7056
+ for (const content of conv.message.content) {
7057
+ if (typeof content === "string") continue;
7058
+ if (content.type === "tool_result") {
7059
+ const toolUseResult = conv.toolUseResult;
7060
+ if (hasAgentId(toolUseResult)) {
7061
+ agentIds.add(toolUseResult.agentId);
7062
+ }
7063
+ }
7064
+ }
7065
+ }
7066
+ const existingAgentIds = /* @__PURE__ */ new Set();
7067
+ for (const conv of session.conversations) {
7068
+ if (conv.type === "x-error") continue;
7069
+ if (conv.type !== "summary" && conv.type !== "file-history-snapshot" && conv.type !== "queue-operation" && conv.type !== "progress" && conv.isSidechain === true && conv.agentId !== void 0) {
7070
+ existingAgentIds.add(conv.agentId);
7071
+ }
7072
+ }
7073
+ const missingAgentIds = Array.from(agentIds).filter(
7074
+ (id) => !existingAgentIds.has(id)
7075
+ );
7076
+ const loadedConversations = [];
7077
+ if (missingAgentIds.length > 0) {
7078
+ const loadedSessions = yield* Effect44.all(
7079
+ missingAgentIds.map(
7080
+ (agentId) => agentSessionRepo.getAgentSessionByAgentId(
7081
+ projectId,
7082
+ agentId,
7083
+ session.id
7084
+ )
7085
+ ),
7086
+ { concurrency: 5 }
7087
+ );
7088
+ for (const sess of loadedSessions) {
7089
+ if (sess) {
7090
+ const validConvs = sess.filter(
7091
+ (c) => c.type === "user" || c.type === "assistant" || c.type === "system"
7092
+ );
7093
+ loadedConversations.push(
7094
+ ...validConvs.map((c) => ({
7095
+ ...c,
7096
+ isSidechain: true
7097
+ // Ensure they are marked as sidechain
7098
+ }))
7099
+ );
7100
+ }
7101
+ }
7102
+ }
7103
+ const allConversations = [
7104
+ ...session.conversations.filter(
7105
+ (conv) => conv.type !== "x-error"
7106
+ ),
7107
+ ...loadedConversations
7108
+ ];
7109
+ const sidechainData = buildSidechainData(allConversations);
7110
+ const toolResultMap = /* @__PURE__ */ new Map();
7111
+ for (const conv of allConversations) {
7112
+ if (conv.type === "summary" || conv.type === "file-history-snapshot" || conv.type === "queue-operation" || conv.type === "progress") {
7113
+ continue;
7114
+ }
7115
+ if (conv.type !== "user") continue;
7116
+ const content = conv.message.content;
7117
+ if (typeof content === "string") continue;
7118
+ for (const msg of content) {
7119
+ if (typeof msg === "string") continue;
7120
+ if (msg.type === "tool_result") {
7121
+ toolResultMap.set(msg.tool_use_id, msg);
7122
+ }
7123
+ }
7124
+ }
6247
7125
  const grouped = groupConsecutiveAssistantMessages(session.conversations);
6248
7126
  const conversationsHtml = grouped.map((group) => {
6249
7127
  if (group.type === "grouped") {
6250
7128
  return renderGroupedAssistantEntries(
6251
- group.entries
7129
+ group.entries,
7130
+ toolResultMap,
7131
+ sidechainData
6252
7132
  );
6253
7133
  }
6254
7134
  const conv = group.entries[0];
@@ -6259,7 +7139,7 @@ var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
6259
7139
  return renderUserEntry(conv);
6260
7140
  }
6261
7141
  if (conv.type === "assistant") {
6262
- return renderAssistantEntry(conv);
7142
+ return renderAssistantEntry(conv, toolResultMap, sidechainData);
6263
7143
  }
6264
7144
  if (conv.type === "system") {
6265
7145
  return renderSystemEntry(conv);
@@ -6422,7 +7302,7 @@ var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
6422
7302
  .markdown-p {
6423
7303
  margin-bottom: 1rem;
6424
7304
  line-height: 1.75;
6425
- word-break: break-all;
7305
+ word-break: break-word;
6426
7306
  }
6427
7307
 
6428
7308
  .inline-code {
@@ -6536,7 +7416,6 @@ var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
6536
7416
  }
6537
7417
 
6538
7418
  .collapsible-content {
6539
- max-height: 1000px;
6540
7419
  overflow: hidden;
6541
7420
  transition: max-height 0.3s ease-out, opacity 0.2s ease-out;
6542
7421
  }
@@ -6692,9 +7571,328 @@ var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
6692
7571
  color: hsl(var(--muted-foreground));
6693
7572
  font-size: 0.875rem;
6694
7573
  }
6695
- </style>
6696
- </head>
6697
- <body>
7574
+
7575
+ /* Enhanced Markdown Styles */
7576
+ .markdown-table {
7577
+ width: 100%;
7578
+ border-collapse: collapse;
7579
+ margin: 1rem 0;
7580
+ font-size: 0.875rem;
7581
+ }
7582
+
7583
+ .markdown-table th,
7584
+ .markdown-table td {
7585
+ border: 1px solid hsl(var(--border));
7586
+ padding: 0.5rem 0.75rem;
7587
+ text-align: left;
7588
+ }
7589
+
7590
+ .markdown-table th {
7591
+ background: hsl(var(--muted) / 0.5);
7592
+ font-weight: 600;
7593
+ }
7594
+
7595
+ .markdown-table tr:nth-child(even) {
7596
+ background: hsl(var(--muted) / 0.2);
7597
+ }
7598
+
7599
+ .markdown-blockquote {
7600
+ border-left: 4px solid hsl(var(--blue-600));
7601
+ padding: 0.75rem 1rem;
7602
+ margin: 1rem 0;
7603
+ background: hsl(var(--muted) / 0.3);
7604
+ color: hsl(var(--muted-foreground));
7605
+ font-style: italic;
7606
+ }
7607
+
7608
+ .markdown-ul,
7609
+ .markdown-ol {
7610
+ margin: 1rem 0;
7611
+ padding-left: 1.5rem;
7612
+ }
7613
+
7614
+ .markdown-ul li,
7615
+ .markdown-ol li {
7616
+ margin-bottom: 0.25rem;
7617
+ line-height: 1.6;
7618
+ }
7619
+
7620
+ .markdown-task-list {
7621
+ list-style: none;
7622
+ padding-left: 0;
7623
+ margin: 1rem 0;
7624
+ }
7625
+
7626
+ .task-item {
7627
+ display: flex;
7628
+ align-items: flex-start;
7629
+ gap: 0.5rem;
7630
+ margin-bottom: 0.25rem;
7631
+ }
7632
+
7633
+ .task-checkbox {
7634
+ margin-top: 0.25rem;
7635
+ width: 1rem;
7636
+ height: 1rem;
7637
+ accent-color: hsl(var(--blue-600));
7638
+ }
7639
+
7640
+ .markdown-hr {
7641
+ border: none;
7642
+ border-top: 2px solid hsl(var(--border));
7643
+ margin: 2rem 0;
7644
+ }
7645
+
7646
+ .markdown-del {
7647
+ text-decoration: line-through;
7648
+ color: hsl(var(--muted-foreground));
7649
+ }
7650
+
7651
+ /* Tool Result Styles */
7652
+ .tool-result-block {
7653
+ margin-top: 0.75rem;
7654
+ border: 1px solid hsl(var(--border));
7655
+ border-radius: 0.375rem;
7656
+ overflow: scroll;
7657
+ }
7658
+
7659
+ .tool-result-error {
7660
+ border-color: hsl(0 84% 60%);
7661
+ }
7662
+
7663
+ .tool-result-header {
7664
+ display: flex;
7665
+ align-items: center;
7666
+ gap: 0.5rem;
7667
+ padding: 0.375rem 0.75rem;
7668
+ background: hsl(var(--muted) / 0.3);
7669
+ font-size: 0.75rem;
7670
+ font-weight: 500;
7671
+ }
7672
+
7673
+ .tool-result-error .tool-result-header {
7674
+ background: hsl(0 84% 60% / 0.1);
7675
+ color: hsl(0 84% 40%);
7676
+ }
7677
+
7678
+ .icon-check {
7679
+ flex-shrink: 0;
7680
+ color: hsl(142 76% 36%);
7681
+ }
7682
+
7683
+ .tool-result-error .icon-check {
7684
+ color: hsl(0 84% 60%);
7685
+ }
7686
+
7687
+ .tool-result-label {
7688
+ font-weight: 500;
7689
+ }
7690
+
7691
+ .tool-result-content {
7692
+ padding: 0.75rem;
7693
+ background: hsl(var(--background));
7694
+ }
7695
+
7696
+ .tool-result-text {
7697
+ font-family: monospace;
7698
+ font-size: 0.75rem;
7699
+ white-space: pre-wrap;
7700
+ word-break: break-word;
7701
+ overflow-wrap: break-word;
7702
+ margin: 0;
7703
+ }
7704
+
7705
+ .tool-result-image {
7706
+ max-width: 100%;
7707
+ height: auto;
7708
+ border-radius: 0.25rem;
7709
+ }
7710
+
7711
+ /* Task Tool Styles */
7712
+ .task-tool-block {
7713
+ border: 1px solid hsl(142 76% 36% / 0.3);
7714
+ background: hsl(142 76% 36% / 0.05);
7715
+ border-radius: 0.5rem;
7716
+ margin-bottom: 0.5rem;
7717
+ overflow: hidden;
7718
+ }
7719
+
7720
+ .task-tool-header {
7721
+ display: flex;
7722
+ align-items: center;
7723
+ gap: 0.5rem;
7724
+ padding: 0.5rem 0.75rem;
7725
+ cursor: pointer;
7726
+ background: hsl(142 76% 36% / 0.1);
7727
+ transition: background 0.2s;
7728
+ }
7729
+
7730
+ .task-tool-header:hover {
7731
+ background: hsl(142 76% 36% / 0.15);
7732
+ }
7733
+
7734
+ .icon-task {
7735
+ color: hsl(142 76% 36%);
7736
+ flex-shrink: 0;
7737
+ }
7738
+
7739
+ .task-tool-name {
7740
+ font-size: 0.875rem;
7741
+ font-weight: 600;
7742
+ color: hsl(142 76% 30%);
7743
+ }
7744
+
7745
+ .task-prompt-preview {
7746
+ flex: 1;
7747
+ font-size: 0.75rem;
7748
+ color: hsl(var(--muted-foreground));
7749
+ overflow: hidden;
7750
+ text-overflow: ellipsis;
7751
+ white-space: nowrap;
7752
+ }
7753
+
7754
+ .task-tool-content {
7755
+ padding: 0.75rem 1rem;
7756
+ border-top: 1px solid hsl(142 76% 36% / 0.2);
7757
+ display: flex;
7758
+ flex-direction: column;
7759
+ gap: 0.75rem;
7760
+ }
7761
+
7762
+ .task-tool-id {
7763
+ font-size: 0.75rem;
7764
+ }
7765
+
7766
+ .task-tool-id code {
7767
+ background: hsl(var(--background) / 0.5);
7768
+ padding: 0.25rem 0.5rem;
7769
+ border-radius: 0.25rem;
7770
+ border: 1px solid hsl(142 76% 36% / 0.2);
7771
+ font-family: monospace;
7772
+ font-size: 0.75rem;
7773
+ }
7774
+
7775
+ .task-prompt {
7776
+ font-size: 0.875rem;
7777
+ }
7778
+
7779
+ .task-prompt-text {
7780
+ background: hsl(var(--background));
7781
+ border: 1px solid hsl(var(--border));
7782
+ border-radius: 0.375rem;
7783
+ padding: 0.75rem;
7784
+ margin-top: 0.5rem;
7785
+ }
7786
+
7787
+ /* Sidechain / Subagent Styles */
7788
+ .sidechain-container {
7789
+ margin-top: 1rem;
7790
+ border: 1px solid hsl(217 91% 60% / 0.3);
7791
+ border-radius: 0.5rem;
7792
+ overflow: hidden;
7793
+ }
7794
+
7795
+ .sidechain-header {
7796
+ display: flex;
7797
+ align-items: center;
7798
+ gap: 0.5rem;
7799
+ padding: 0.5rem 0.75rem;
7800
+ background: hsl(217 91% 60% / 0.1);
7801
+ color: hsl(217 91% 40%);
7802
+ font-size: 0.8rem;
7803
+ font-weight: 500;
7804
+ cursor: pointer;
7805
+ }
7806
+
7807
+ .sidechain-header:hover {
7808
+ background: hsl(217 91% 60% / 0.15);
7809
+ }
7810
+
7811
+ .icon-layers {
7812
+ flex-shrink: 0;
7813
+ }
7814
+
7815
+ .sidechain-content {
7816
+ padding: 0.75rem;
7817
+ background: hsl(217 91% 60% / 0.02);
7818
+ border-top: 1px solid hsl(217 91% 60% / 0.2);
7819
+ }
7820
+
7821
+ .sidechain-entry {
7822
+ margin-left: 1rem;
7823
+ padding: 0.5rem 0.75rem;
7824
+ border-left: 2px solid hsl(217 91% 60% / 0.3);
7825
+ margin-bottom: 0.5rem;
7826
+ }
7827
+
7828
+ .sidechain-entry:last-child {
7829
+ margin-bottom: 0;
7830
+ }
7831
+
7832
+ .sidechain-entry-header {
7833
+ display: flex;
7834
+ align-items: center;
7835
+ gap: 0.5rem;
7836
+ margin-bottom: 0.25rem;
7837
+ font-size: 0.75rem;
7838
+ }
7839
+
7840
+ .sidechain-role {
7841
+ font-weight: 600;
7842
+ padding: 0.125rem 0.375rem;
7843
+ border-radius: 0.25rem;
7844
+ }
7845
+
7846
+ .sidechain-user-entry .sidechain-role {
7847
+ background: hsl(var(--muted));
7848
+ color: hsl(var(--foreground));
7849
+ }
7850
+
7851
+ .sidechain-assistant-entry .sidechain-role {
7852
+ background: hsl(217 91% 60% / 0.1);
7853
+ color: hsl(217 91% 40%);
7854
+ }
7855
+
7856
+ .sidechain-system-entry .sidechain-role {
7857
+ background: hsl(var(--muted) / 0.5);
7858
+ color: hsl(var(--muted-foreground));
7859
+ }
7860
+
7861
+ .sidechain-timestamp {
7862
+ color: hsl(var(--muted-foreground));
7863
+ }
7864
+
7865
+ .sidechain-entry-content {
7866
+ font-size: 0.875rem;
7867
+ }
7868
+
7869
+ .sidechain-entry .thinking-block,
7870
+ .sidechain-entry .tool-use-block {
7871
+ margin: 0.5rem 0;
7872
+ font-size: 0.8rem;
7873
+ }
7874
+
7875
+ .sidechain-entry .thinking-header,
7876
+ .sidechain-entry .tool-use-header {
7877
+ padding: 0.375rem 0.5rem;
7878
+ }
7879
+
7880
+ .sidechain-entry .thinking-content,
7881
+ .sidechain-entry .tool-use-content {
7882
+ padding: 0.5rem;
7883
+ }
7884
+
7885
+ /* Optimize content display */
7886
+ .entry-content > *:first-child {
7887
+ margin-top: 0;
7888
+ }
7889
+
7890
+ .entry-content > *:last-child {
7891
+ margin-bottom: 0;
7892
+ }
7893
+ </style>
7894
+ </head>
7895
+ <body>
6698
7896
  <div class="header">
6699
7897
  <div class="header-top">
6700
7898
  <h1>Claude Code Session Export</h1>
@@ -6763,11 +7961,12 @@ var generateSessionHtml = (session, projectId) => Effect42.gen(function* () {
6763
7961
  });
6764
7962
 
6765
7963
  // src/server/core/session/presentation/SessionController.ts
6766
- var LayerImpl28 = Effect43.gen(function* () {
7964
+ var LayerImpl29 = Effect45.gen(function* () {
6767
7965
  const sessionRepository = yield* SessionRepository;
6768
- const fs = yield* FileSystem14.FileSystem;
7966
+ const agentSessionRepository = yield* AgentSessionRepository;
7967
+ const fs = yield* FileSystem16.FileSystem;
6769
7968
  const eventBus = yield* EventBus;
6770
- const getSession = (options) => Effect43.gen(function* () {
7969
+ const getSession = (options) => Effect45.gen(function* () {
6771
7970
  const { projectId, sessionId } = options;
6772
7971
  const { session } = yield* sessionRepository.getSession(
6773
7972
  projectId,
@@ -6778,7 +7977,7 @@ var LayerImpl28 = Effect43.gen(function* () {
6778
7977
  response: { session }
6779
7978
  };
6780
7979
  });
6781
- const exportSessionHtml = (options) => Effect43.gen(function* () {
7980
+ const exportSessionHtml = (options) => Effect45.gen(function* () {
6782
7981
  const { projectId, sessionId } = options;
6783
7982
  const { session } = yield* sessionRepository.getSession(
6784
7983
  projectId,
@@ -6790,13 +7989,17 @@ var LayerImpl28 = Effect43.gen(function* () {
6790
7989
  response: { error: "Session not found" }
6791
7990
  };
6792
7991
  }
6793
- const html = yield* generateSessionHtml(session, projectId);
7992
+ const html = yield* generateSessionHtml(
7993
+ session,
7994
+ projectId,
7995
+ agentSessionRepository
7996
+ );
6794
7997
  return {
6795
7998
  status: 200,
6796
7999
  response: { html }
6797
8000
  };
6798
8001
  });
6799
- const deleteSession = (options) => Effect43.gen(function* () {
8002
+ const deleteSession = (options) => Effect45.gen(function* () {
6800
8003
  const { projectId, sessionId } = options;
6801
8004
  const sessionPath = decodeSessionId(projectId, sessionId);
6802
8005
  const exists = yield* fs.exists(sessionPath);
@@ -6807,9 +8010,9 @@ var LayerImpl28 = Effect43.gen(function* () {
6807
8010
  };
6808
8011
  }
6809
8012
  const deleteResult = yield* fs.remove(sessionPath).pipe(
6810
- Effect43.map(() => ({ success: true, error: null })),
6811
- Effect43.catchAll(
6812
- (error) => Effect43.succeed({
8013
+ Effect45.map(() => ({ success: true, error: null })),
8014
+ Effect45.catchAll(
8015
+ (error) => Effect45.succeed({
6813
8016
  success: false,
6814
8017
  error: `Failed to delete session: ${error.message}`
6815
8018
  })
@@ -6833,9 +8036,347 @@ var LayerImpl28 = Effect43.gen(function* () {
6833
8036
  deleteSession
6834
8037
  };
6835
8038
  });
6836
- var SessionController = class extends Context34.Tag("SessionController")() {
8039
+ var SessionController = class extends Context35.Tag("SessionController")() {
6837
8040
  static {
6838
- this.Live = Layer36.effect(this, LayerImpl28);
8041
+ this.Live = Layer37.effect(this, LayerImpl29);
8042
+ }
8043
+ };
8044
+
8045
+ // src/server/core/tasks/presentation/TasksController.ts
8046
+ import { Context as Context37, Effect as Effect47, Layer as Layer39 } from "effect";
8047
+
8048
+ // src/server/core/tasks/services/TasksService.ts
8049
+ import { homedir as homedir5 } from "node:os";
8050
+ import { join as join3 } from "node:path";
8051
+ import { FileSystem as FileSystem17, Path as Path19 } from "@effect/platform";
8052
+ import { Context as Context36, Effect as Effect46, Layer as Layer38, Option as Option4 } from "effect";
8053
+
8054
+ // src/server/core/tasks/schema.ts
8055
+ import { z as z26 } from "zod";
8056
+ var TaskStatusSchema = z26.enum([
8057
+ "pending",
8058
+ "in_progress",
8059
+ "completed",
8060
+ "failed"
8061
+ ]);
8062
+ var TaskSchema = z26.object({
8063
+ id: z26.string(),
8064
+ subject: z26.string(),
8065
+ description: z26.string().optional(),
8066
+ status: TaskStatusSchema,
8067
+ owner: z26.string().optional(),
8068
+ blocks: z26.array(z26.string()).optional(),
8069
+ blockedBy: z26.array(z26.string()).optional(),
8070
+ metadata: z26.record(z26.string(), z26.any()).optional(),
8071
+ activeForm: z26.string().optional()
8072
+ });
8073
+ var TaskCreateSchema = z26.object({
8074
+ subject: z26.string(),
8075
+ description: z26.string().optional(),
8076
+ activeForm: z26.string().optional(),
8077
+ metadata: z26.record(z26.string(), z26.any()).optional()
8078
+ });
8079
+ var TaskUpdateSchema = z26.object({
8080
+ taskId: z26.string(),
8081
+ status: TaskStatusSchema.optional(),
8082
+ subject: z26.string().optional(),
8083
+ description: z26.string().optional(),
8084
+ activeForm: z26.string().optional(),
8085
+ owner: z26.string().optional(),
8086
+ addBlockedBy: z26.array(z26.string()).optional(),
8087
+ addBlocks: z26.array(z26.string()).optional(),
8088
+ metadata: z26.record(z26.string(), z26.any()).optional()
8089
+ });
8090
+
8091
+ // src/server/core/tasks/services/TasksService.ts
8092
+ var TASKS_DIR_NAME = "tasks";
8093
+ var PROJECTS_DIR_NAME = "projects";
8094
+ var CLAUDE_DIR_NAME = ".claude";
8095
+ var TasksService = class extends Context36.Tag("TasksService")() {
8096
+ static {
8097
+ this.Live = Layer38.effect(
8098
+ this,
8099
+ Effect46.gen(function* () {
8100
+ const fs = yield* FileSystem17.FileSystem;
8101
+ const path = yield* Path19.Path;
8102
+ const getClaudeDir = () => Effect46.succeed(join3(homedir5(), CLAUDE_DIR_NAME));
8103
+ const normalizeProjectPath = (projectPath) => {
8104
+ const normalized = projectPath.replaceAll(path.sep, "-");
8105
+ return normalized.startsWith("-") ? normalized : `-${normalized}`;
8106
+ };
8107
+ const resolveProjectUuid = (projectPath, specificSessionId) => Effect46.gen(function* () {
8108
+ const claudeDir = yield* getClaudeDir();
8109
+ if (specificSessionId) {
8110
+ const sessionTasksDir = path.join(
8111
+ claudeDir,
8112
+ TASKS_DIR_NAME,
8113
+ specificSessionId
8114
+ );
8115
+ if (yield* fs.exists(sessionTasksDir)) {
8116
+ return Option4.some(specificSessionId);
8117
+ }
8118
+ return Option4.none();
8119
+ }
8120
+ const isMetadataPath = projectPath.includes(join3(CLAUDE_DIR_NAME, PROJECTS_DIR_NAME)) && projectPath.split(path.sep).pop()?.startsWith("-");
8121
+ let projectMetaDir;
8122
+ if (isMetadataPath && (yield* fs.exists(projectPath))) {
8123
+ projectMetaDir = projectPath;
8124
+ } else {
8125
+ const identifier = normalizeProjectPath(projectPath);
8126
+ projectMetaDir = path.join(
8127
+ claudeDir,
8128
+ PROJECTS_DIR_NAME,
8129
+ identifier
8130
+ );
8131
+ }
8132
+ const exists = yield* fs.exists(projectMetaDir);
8133
+ if (!exists) {
8134
+ return Option4.none();
8135
+ }
8136
+ const files = yield* fs.readDirectory(projectMetaDir);
8137
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
8138
+ const candidates = files.filter((f) => uuidPattern.test(f));
8139
+ if (candidates.length === 0) {
8140
+ return Option4.none();
8141
+ }
8142
+ const candidateInfo = yield* Effect46.all(
8143
+ candidates.map(
8144
+ (file) => Effect46.gen(function* () {
8145
+ const fullPath = path.join(projectMetaDir, file);
8146
+ const stat = yield* fs.stat(fullPath);
8147
+ const match = file.match(uuidPattern);
8148
+ const uuid = match ? match[0] : file;
8149
+ const tasksPath = path.join(claudeDir, TASKS_DIR_NAME, uuid);
8150
+ const hasTasks = yield* fs.exists(tasksPath);
8151
+ return {
8152
+ file,
8153
+ uuid,
8154
+ mtime: Option4.getOrElse(stat.mtime, () => /* @__PURE__ */ new Date(0)),
8155
+ hasTasks
8156
+ };
8157
+ })
8158
+ ),
8159
+ { concurrency: "unbounded" }
8160
+ );
8161
+ const sorted = candidateInfo.sort((a, b) => {
8162
+ if (a.hasTasks && !b.hasTasks) return -1;
8163
+ if (!a.hasTasks && b.hasTasks) return 1;
8164
+ return b.mtime.getTime() - a.mtime.getTime();
8165
+ });
8166
+ const best = sorted[0];
8167
+ if (!best) {
8168
+ return Option4.none();
8169
+ }
8170
+ return Option4.some(best.uuid);
8171
+ });
8172
+ const resolveProjectUuidOrFail = (projectPath, specificSessionId) => Effect46.gen(function* () {
8173
+ const uuidOption = yield* resolveProjectUuid(
8174
+ projectPath,
8175
+ specificSessionId
8176
+ );
8177
+ if (Option4.isNone(uuidOption)) {
8178
+ if (specificSessionId) {
8179
+ return yield* Effect46.fail(
8180
+ new Error(
8181
+ `Requested session ${specificSessionId} has no tasks directory`
8182
+ )
8183
+ );
8184
+ }
8185
+ const claudeDir = yield* getClaudeDir();
8186
+ const identifier = normalizeProjectPath(projectPath);
8187
+ const projectMetaDir = path.join(
8188
+ claudeDir,
8189
+ PROJECTS_DIR_NAME,
8190
+ identifier
8191
+ );
8192
+ return yield* Effect46.fail(
8193
+ new Error(
8194
+ `Project metadata directory not found or no UUID: ${projectMetaDir}`
8195
+ )
8196
+ );
8197
+ }
8198
+ return uuidOption.value;
8199
+ });
8200
+ const getTasksDir = (projectPath, specificSessionId) => Effect46.gen(function* () {
8201
+ const claudeDir = yield* getClaudeDir();
8202
+ const uuidOption = yield* resolveProjectUuid(
8203
+ projectPath,
8204
+ specificSessionId
8205
+ );
8206
+ return Option4.map(
8207
+ uuidOption,
8208
+ (uuid) => path.join(claudeDir, TASKS_DIR_NAME, uuid)
8209
+ );
8210
+ });
8211
+ const getTasksDirOrFail = (projectPath, specificSessionId) => Effect46.gen(function* () {
8212
+ const claudeDir = yield* getClaudeDir();
8213
+ const uuid = yield* resolveProjectUuidOrFail(
8214
+ projectPath,
8215
+ specificSessionId
8216
+ );
8217
+ return path.join(claudeDir, TASKS_DIR_NAME, uuid);
8218
+ });
8219
+ const listTasks = (projectPath, specificSessionId) => Effect46.gen(function* () {
8220
+ const tasksDirOption = yield* getTasksDir(
8221
+ projectPath,
8222
+ specificSessionId
8223
+ );
8224
+ if (Option4.isNone(tasksDirOption)) {
8225
+ return [];
8226
+ }
8227
+ const tasksDir = tasksDirOption.value;
8228
+ const exists = yield* fs.exists(tasksDir);
8229
+ if (!exists) {
8230
+ return [];
8231
+ }
8232
+ const files = yield* fs.readDirectory(tasksDir);
8233
+ const tasks = [];
8234
+ for (const file of files) {
8235
+ if (!file.endsWith(".json")) continue;
8236
+ const content = yield* fs.readFileString(path.join(tasksDir, file));
8237
+ try {
8238
+ const task = JSON.parse(content);
8239
+ const parsed = TaskSchema.safeParse(task);
8240
+ if (parsed.success) {
8241
+ tasks.push(parsed.data);
8242
+ } else {
8243
+ console.warn(`Invalid task file ${file}:`, parsed.error);
8244
+ const fallbackTask = {
8245
+ id: typeof task === "object" && task !== null && "id" in task && typeof task.id === "string" ? task.id : file.replace(".json", ""),
8246
+ subject: typeof task === "object" && task !== null && "subject" in task && typeof task.subject === "string" ? task.subject : typeof task === "object" && task !== null && "title" in task && typeof task.title === "string" ? task.title : "Invalid Task Schema",
8247
+ description: `Validation Error: ${JSON.stringify(parsed.error.format())}. Raw: ${JSON.stringify(task)}`,
8248
+ status: typeof task === "object" && task !== null && "status" in task && typeof task.status === "string" && (task.status === "pending" || task.status === "in_progress" || task.status === "completed" || task.status === "failed") ? task.status : "failed",
8249
+ blocks: [],
8250
+ blockedBy: []
8251
+ };
8252
+ tasks.push(fallbackTask);
8253
+ }
8254
+ } catch (e) {
8255
+ console.error(`Failed to parse task file ${file}`, e);
8256
+ const fallbackTask = {
8257
+ id: file.replace(".json", ""),
8258
+ subject: "Corrupted Task File",
8259
+ description: String(e),
8260
+ status: "failed",
8261
+ blocks: [],
8262
+ blockedBy: []
8263
+ };
8264
+ tasks.push(fallbackTask);
8265
+ }
8266
+ }
8267
+ return tasks.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10));
8268
+ });
8269
+ const getTask = (projectPath, taskId, specificSessionId) => Effect46.gen(function* () {
8270
+ const tasksDir = yield* getTasksDirOrFail(
8271
+ projectPath,
8272
+ specificSessionId
8273
+ );
8274
+ const taskFile = path.join(tasksDir, `${taskId}.json`);
8275
+ const exists = yield* fs.exists(taskFile);
8276
+ if (!exists) {
8277
+ return yield* Effect46.fail(new Error(`Task ${taskId} not found`));
8278
+ }
8279
+ const content = yield* fs.readFileString(taskFile);
8280
+ const task = JSON.parse(content);
8281
+ return yield* Effect46.try(() => TaskSchema.parse(task));
8282
+ });
8283
+ const createTask = (projectPath, taskDef, specificSessionId) => Effect46.gen(function* () {
8284
+ const tasksDir = yield* getTasksDirOrFail(
8285
+ projectPath,
8286
+ specificSessionId
8287
+ );
8288
+ const dirExists = yield* fs.exists(tasksDir);
8289
+ if (!dirExists) {
8290
+ yield* fs.makeDirectory(tasksDir, { recursive: true });
8291
+ }
8292
+ const files = yield* fs.readDirectory(tasksDir);
8293
+ let maxId = 0;
8294
+ for (const file of files) {
8295
+ if (file.endsWith(".json")) {
8296
+ const idPart = file.replace(".json", "");
8297
+ const idNum = parseInt(idPart, 10);
8298
+ if (!Number.isNaN(idNum) && idNum > maxId) {
8299
+ maxId = idNum;
8300
+ }
8301
+ }
8302
+ }
8303
+ const newId = (maxId + 1).toString();
8304
+ const newTask = {
8305
+ id: newId,
8306
+ status: "pending",
8307
+ blocks: [],
8308
+ blockedBy: [],
8309
+ ...taskDef
8310
+ };
8311
+ const filePath = path.join(tasksDir, `${newId}.json`);
8312
+ yield* fs.writeFileString(filePath, JSON.stringify(newTask, null, 2));
8313
+ return newTask;
8314
+ });
8315
+ const updateTask = (projectPath, update, specificSessionId) => Effect46.gen(function* () {
8316
+ const tasksDir = yield* getTasksDirOrFail(
8317
+ projectPath,
8318
+ specificSessionId
8319
+ );
8320
+ const filePath = path.join(tasksDir, `${update.taskId}.json`);
8321
+ const exists = yield* fs.exists(filePath);
8322
+ if (!exists) {
8323
+ return yield* Effect46.fail(
8324
+ new Error(`Task ${update.taskId} not found`)
8325
+ );
8326
+ }
8327
+ const content = yield* fs.readFileString(filePath);
8328
+ const currentTask = TaskSchema.parse(JSON.parse(content));
8329
+ const updatedTask = {
8330
+ ...currentTask,
8331
+ // User cannot update status via Viewer, it is managed by Claude Agent
8332
+ status: currentTask.status,
8333
+ subject: update.subject ?? currentTask.subject,
8334
+ description: update.description ?? currentTask.description,
8335
+ activeForm: update.activeForm ?? currentTask.activeForm,
8336
+ owner: update.owner ?? currentTask.owner,
8337
+ blockedBy: update.addBlockedBy ? [...currentTask.blockedBy || [], ...update.addBlockedBy] : currentTask.blockedBy,
8338
+ blocks: update.addBlocks ? [...currentTask.blocks || [], ...update.addBlocks] : currentTask.blocks,
8339
+ metadata: update.metadata ? { ...currentTask.metadata, ...update.metadata } : currentTask.metadata
8340
+ };
8341
+ if (updatedTask.metadata) {
8342
+ for (const key in updatedTask.metadata) {
8343
+ if (updatedTask.metadata[key] === null) {
8344
+ delete updatedTask.metadata[key];
8345
+ }
8346
+ }
8347
+ }
8348
+ yield* fs.writeFileString(
8349
+ filePath,
8350
+ JSON.stringify(updatedTask, null, 2)
8351
+ );
8352
+ return updatedTask;
8353
+ });
8354
+ return {
8355
+ listTasks,
8356
+ getTask,
8357
+ createTask,
8358
+ updateTask
8359
+ };
8360
+ })
8361
+ );
8362
+ }
8363
+ };
8364
+
8365
+ // src/server/core/tasks/presentation/TasksController.ts
8366
+ var make = Effect47.gen(function* () {
8367
+ const service = yield* TasksService;
8368
+ const listTasks = (projectPath, specificSessionId) => service.listTasks(projectPath, specificSessionId);
8369
+ const createTask = (projectPath, task, specificSessionId) => service.createTask(projectPath, task, specificSessionId);
8370
+ const updateTask = (projectPath, task, specificSessionId) => service.updateTask(projectPath, task, specificSessionId);
8371
+ return {
8372
+ listTasks,
8373
+ createTask,
8374
+ updateTask
8375
+ };
8376
+ });
8377
+ var TasksController = class extends Context37.Tag("TasksController")() {
8378
+ static {
8379
+ this.Live = Layer39.effect(this, make);
6839
8380
  }
6840
8381
  };
6841
8382
 
@@ -6844,12 +8385,12 @@ import { Hono } from "hono";
6844
8385
  var honoApp = new Hono();
6845
8386
 
6846
8387
  // src/server/hono/initialize.ts
6847
- import { Context as Context35, Effect as Effect44, Layer as Layer37, Ref as Ref13, Schedule as Schedule2 } from "effect";
6848
- var InitializeService = class extends Context35.Tag("InitializeService")() {
8388
+ import { Context as Context38, Effect as Effect48, Layer as Layer40, Ref as Ref14, Schedule as Schedule2 } from "effect";
8389
+ var InitializeService = class extends Context38.Tag("InitializeService")() {
6849
8390
  static {
6850
- this.Live = Layer37.effect(
8391
+ this.Live = Layer40.effect(
6851
8392
  this,
6852
- Effect44.gen(function* () {
8393
+ Effect48.gen(function* () {
6853
8394
  const eventBus = yield* EventBus;
6854
8395
  const fileWatcher = yield* FileWatcherService;
6855
8396
  const projectRepository = yield* ProjectRepository;
@@ -6857,22 +8398,24 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6857
8398
  const projectMetaService = yield* ProjectMetaService;
6858
8399
  const sessionMetaService = yield* SessionMetaService;
6859
8400
  const virtualConversationDatabase = yield* VirtualConversationDatabase;
6860
- const listenersRef = yield* Ref13.make({});
8401
+ const rateLimitAutoScheduleService = yield* RateLimitAutoScheduleService;
8402
+ const listenersRef = yield* Ref14.make({});
6861
8403
  const startInitialization = () => {
6862
- return Effect44.gen(function* () {
8404
+ return Effect48.gen(function* () {
6863
8405
  yield* fileWatcher.startWatching();
6864
- const daemon = Effect44.repeat(
8406
+ yield* rateLimitAutoScheduleService.start();
8407
+ const daemon = Effect48.repeat(
6865
8408
  eventBus.emit("heartbeat", {}),
6866
8409
  Schedule2.fixed("10 seconds")
6867
8410
  );
6868
8411
  console.log("start heartbeat");
6869
- yield* Effect44.forkDaemon(daemon);
8412
+ yield* Effect48.forkDaemon(daemon);
6870
8413
  console.log("after starting heartbeat fork");
6871
8414
  const onSessionChanged = (event) => {
6872
- Effect44.runFork(
8415
+ Effect48.runFork(
6873
8416
  projectMetaService.invalidateProject(event.projectId)
6874
8417
  );
6875
- Effect44.runFork(
8418
+ Effect48.runFork(
6876
8419
  sessionMetaService.invalidateSession(
6877
8420
  event.projectId,
6878
8421
  event.sessionId
@@ -6881,7 +8424,7 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6881
8424
  };
6882
8425
  const onSessionProcessChanged = (event) => {
6883
8426
  if ((event.changed.type === "completed" || event.changed.type === "paused") && event.changed.sessionId !== void 0) {
6884
- Effect44.runFork(
8427
+ Effect48.runFork(
6885
8428
  virtualConversationDatabase.deleteVirtualConversations(
6886
8429
  event.changed.sessionId
6887
8430
  )
@@ -6889,18 +8432,18 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6889
8432
  return;
6890
8433
  }
6891
8434
  };
6892
- yield* Ref13.set(listenersRef, {
8435
+ yield* Ref14.set(listenersRef, {
6893
8436
  sessionChanged: onSessionChanged,
6894
8437
  sessionProcessChanged: onSessionProcessChanged
6895
8438
  });
6896
8439
  yield* eventBus.on("sessionChanged", onSessionChanged);
6897
8440
  yield* eventBus.on("sessionProcessChanged", onSessionProcessChanged);
6898
- yield* Effect44.gen(function* () {
8441
+ yield* Effect48.gen(function* () {
6899
8442
  console.log("Initializing projects cache");
6900
8443
  const { projects } = yield* projectRepository.getProjects();
6901
8444
  console.log(`${projects.length} projects cache initialized`);
6902
8445
  console.log("Initializing sessions cache");
6903
- const results = yield* Effect44.all(
8446
+ const results = yield* Effect48.all(
6904
8447
  projects.map(
6905
8448
  (project) => sessionRepository.getSessions(project.id)
6906
8449
  ),
@@ -6912,13 +8455,13 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6912
8455
  );
6913
8456
  console.log(`${totalSessions} sessions cache initialized`);
6914
8457
  }).pipe(
6915
- Effect44.catchAll(() => Effect44.void),
6916
- Effect44.withSpan("initialize-cache")
8458
+ Effect48.catchAll(() => Effect48.void),
8459
+ Effect48.withSpan("initialize-cache")
6917
8460
  );
6918
- }).pipe(Effect44.withSpan("start-initialization"));
8461
+ }).pipe(Effect48.withSpan("start-initialization"));
6919
8462
  };
6920
- const stopCleanup = () => Effect44.gen(function* () {
6921
- const listeners = yield* Ref13.get(listenersRef);
8463
+ const stopCleanup = () => Effect48.gen(function* () {
8464
+ const listeners = yield* Ref14.get(listenersRef);
6922
8465
  if (listeners.sessionChanged) {
6923
8466
  yield* eventBus.off("sessionChanged", listeners.sessionChanged);
6924
8467
  }
@@ -6928,7 +8471,8 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6928
8471
  listeners.sessionProcessChanged
6929
8472
  );
6930
8473
  }
6931
- yield* Ref13.set(listenersRef, {});
8474
+ yield* Ref14.set(listenersRef, {});
8475
+ yield* rateLimitAutoScheduleService.stop();
6932
8476
  yield* fileWatcher.stop();
6933
8477
  });
6934
8478
  return {
@@ -6941,7 +8485,7 @@ var InitializeService = class extends Context35.Tag("InitializeService")() {
6941
8485
  };
6942
8486
 
6943
8487
  // src/server/hono/middleware/auth.middleware.ts
6944
- import { Context as Context36, Effect as Effect45, Layer as Layer38 } from "effect";
8488
+ import { Context as Context39, Effect as Effect49, Layer as Layer41 } from "effect";
6945
8489
  import { getCookie } from "hono/cookie";
6946
8490
  import { createMiddleware } from "hono/factory";
6947
8491
  var generateSessionToken = (password) => {
@@ -6956,9 +8500,9 @@ var PUBLIC_API_ROUTES = [
6956
8500
  // Allow config access for theme/locale loading
6957
8501
  "/api/version"
6958
8502
  ];
6959
- var LayerImpl29 = Effect45.gen(function* () {
8503
+ var LayerImpl30 = Effect49.gen(function* () {
6960
8504
  const ccvOptionsService = yield* CcvOptionsService;
6961
- return Effect45.gen(function* () {
8505
+ return Effect49.gen(function* () {
6962
8506
  const anthPassword = yield* ccvOptionsService.getCcvOptions("password");
6963
8507
  const authEnabled = anthPassword !== void 0;
6964
8508
  const validSessionToken = generateSessionToken(anthPassword);
@@ -6986,77 +8530,77 @@ var LayerImpl29 = Effect45.gen(function* () {
6986
8530
  };
6987
8531
  });
6988
8532
  });
6989
- var AuthMiddleware = class extends Context36.Tag("AuthMiddleware")() {
8533
+ var AuthMiddleware = class extends Context39.Tag("AuthMiddleware")() {
6990
8534
  static {
6991
- this.Live = Layer38.effect(this, LayerImpl29);
8535
+ this.Live = Layer41.effect(this, LayerImpl30);
6992
8536
  }
6993
8537
  };
6994
8538
 
6995
8539
  // src/server/hono/route.ts
6996
8540
  import { zValidator } from "@hono/zod-validator";
6997
- import { Effect as Effect47, Runtime as Runtime3 } from "effect";
8541
+ import { Effect as Effect51, Runtime as Runtime3 } from "effect";
6998
8542
  import { deleteCookie, getCookie as getCookie3, setCookie as setCookie2 } from "hono/cookie";
6999
8543
  import { streamSSE } from "hono/streaming";
7000
8544
  import prexit from "prexit";
7001
- import { z as z29 } from "zod";
8545
+ import { z as z31 } from "zod";
7002
8546
 
7003
8547
  // src/server/core/claude-code/schema.ts
7004
- import { z as z25 } from "zod";
7005
- var mediaTypeSchema = z25.enum([
8548
+ import { z as z27 } from "zod";
8549
+ var mediaTypeSchema = z27.enum([
7006
8550
  "image/png",
7007
8551
  "image/jpeg",
7008
8552
  "image/gif",
7009
8553
  "image/webp"
7010
8554
  ]);
7011
- var imageBlockSchema = z25.object({
7012
- type: z25.literal("image"),
7013
- source: z25.object({
7014
- type: z25.literal("base64"),
8555
+ var imageBlockSchema = z27.object({
8556
+ type: z27.literal("image"),
8557
+ source: z27.object({
8558
+ type: z27.literal("base64"),
7015
8559
  media_type: mediaTypeSchema,
7016
- data: z25.string()
8560
+ data: z27.string()
7017
8561
  })
7018
8562
  });
7019
- var documentBlockSchema = z25.object({
7020
- type: z25.literal("document"),
7021
- source: z25.union([
7022
- z25.object({
7023
- type: z25.literal("text"),
7024
- media_type: z25.enum(["text/plain"]),
7025
- data: z25.string()
8563
+ var documentBlockSchema = z27.object({
8564
+ type: z27.literal("document"),
8565
+ source: z27.union([
8566
+ z27.object({
8567
+ type: z27.literal("text"),
8568
+ media_type: z27.enum(["text/plain"]),
8569
+ data: z27.string()
7026
8570
  }),
7027
- z25.object({
7028
- type: z25.literal("base64"),
7029
- media_type: z25.enum(["application/pdf"]),
7030
- data: z25.string()
8571
+ z27.object({
8572
+ type: z27.literal("base64"),
8573
+ media_type: z27.enum(["application/pdf"]),
8574
+ data: z27.string()
7031
8575
  })
7032
8576
  ])
7033
8577
  });
7034
- var userMessageInputSchema = z25.object({
7035
- text: z25.string().min(1),
7036
- images: z25.array(imageBlockSchema).optional(),
7037
- documents: z25.array(documentBlockSchema).optional()
8578
+ var userMessageInputSchema = z27.object({
8579
+ text: z27.string().min(1),
8580
+ images: z27.array(imageBlockSchema).optional(),
8581
+ documents: z27.array(documentBlockSchema).optional()
7038
8582
  });
7039
8583
 
7040
8584
  // src/server/core/git/schema.ts
7041
- import { z as z26 } from "zod";
7042
- var CommitRequestSchema = z26.object({
7043
- projectId: z26.string().min(1),
7044
- files: z26.array(z26.string().min(1)).min(1),
7045
- message: z26.string().trim().min(1)
8585
+ import { z as z28 } from "zod";
8586
+ var CommitRequestSchema = z28.object({
8587
+ projectId: z28.string().min(1),
8588
+ files: z28.array(z28.string().min(1)).min(1),
8589
+ message: z28.string().trim().min(1)
7046
8590
  });
7047
- var PushRequestSchema = z26.object({
7048
- projectId: z26.string().min(1)
8591
+ var PushRequestSchema = z28.object({
8592
+ projectId: z28.string().min(1)
7049
8593
  });
7050
- var CommitResultSuccessSchema = z26.object({
7051
- success: z26.literal(true),
7052
- commitSha: z26.string().length(40),
7053
- filesCommitted: z26.number().int().positive(),
7054
- message: z26.string()
8594
+ var CommitResultSuccessSchema = z28.object({
8595
+ success: z28.literal(true),
8596
+ commitSha: z28.string().length(40),
8597
+ filesCommitted: z28.number().int().positive(),
8598
+ message: z28.string()
7055
8599
  });
7056
- var CommitResultErrorSchema = z26.object({
7057
- success: z26.literal(false),
7058
- error: z26.string(),
7059
- errorCode: z26.enum([
8600
+ var CommitResultErrorSchema = z28.object({
8601
+ success: z28.literal(false),
8602
+ error: z28.string(),
8603
+ errorCode: z28.enum([
7060
8604
  "EMPTY_MESSAGE",
7061
8605
  "NO_FILES",
7062
8606
  "PROJECT_NOT_FOUND",
@@ -7064,22 +8608,22 @@ var CommitResultErrorSchema = z26.object({
7064
8608
  "HOOK_FAILED",
7065
8609
  "GIT_COMMAND_ERROR"
7066
8610
  ]),
7067
- details: z26.string().optional()
8611
+ details: z28.string().optional()
7068
8612
  });
7069
- var CommitResultSchema = z26.discriminatedUnion("success", [
8613
+ var CommitResultSchema = z28.discriminatedUnion("success", [
7070
8614
  CommitResultSuccessSchema,
7071
8615
  CommitResultErrorSchema
7072
8616
  ]);
7073
- var PushResultSuccessSchema = z26.object({
7074
- success: z26.literal(true),
7075
- remote: z26.string(),
7076
- branch: z26.string(),
7077
- objectsPushed: z26.number().int().optional()
8617
+ var PushResultSuccessSchema = z28.object({
8618
+ success: z28.literal(true),
8619
+ remote: z28.string(),
8620
+ branch: z28.string(),
8621
+ objectsPushed: z28.number().int().optional()
7078
8622
  });
7079
- var PushResultErrorSchema = z26.object({
7080
- success: z26.literal(false),
7081
- error: z26.string(),
7082
- errorCode: z26.enum([
8623
+ var PushResultErrorSchema = z28.object({
8624
+ success: z28.literal(false),
8625
+ error: z28.string(),
8626
+ errorCode: z28.enum([
7083
8627
  "PROJECT_NOT_FOUND",
7084
8628
  "NOT_A_REPOSITORY",
7085
8629
  "NO_UPSTREAM",
@@ -7089,26 +8633,26 @@ var PushResultErrorSchema = z26.object({
7089
8633
  "TIMEOUT",
7090
8634
  "GIT_COMMAND_ERROR"
7091
8635
  ]),
7092
- details: z26.string().optional()
8636
+ details: z28.string().optional()
7093
8637
  });
7094
- var PushResultSchema = z26.discriminatedUnion("success", [
8638
+ var PushResultSchema = z28.discriminatedUnion("success", [
7095
8639
  PushResultSuccessSchema,
7096
8640
  PushResultErrorSchema
7097
8641
  ]);
7098
- var CommitAndPushResultSuccessSchema = z26.object({
7099
- success: z26.literal(true),
7100
- commitSha: z26.string().length(40),
7101
- filesCommitted: z26.number().int().positive(),
7102
- message: z26.string(),
7103
- remote: z26.string(),
7104
- branch: z26.string()
8642
+ var CommitAndPushResultSuccessSchema = z28.object({
8643
+ success: z28.literal(true),
8644
+ commitSha: z28.string().length(40),
8645
+ filesCommitted: z28.number().int().positive(),
8646
+ message: z28.string(),
8647
+ remote: z28.string(),
8648
+ branch: z28.string()
7105
8649
  });
7106
- var CommitAndPushResultErrorSchema = z26.object({
7107
- success: z26.literal(false),
7108
- commitSucceeded: z26.boolean(),
7109
- commitSha: z26.string().length(40).optional(),
7110
- error: z26.string(),
7111
- errorCode: z26.enum([
8650
+ var CommitAndPushResultErrorSchema = z28.object({
8651
+ success: z28.literal(false),
8652
+ commitSucceeded: z28.boolean(),
8653
+ commitSha: z28.string().length(40).optional(),
8654
+ error: z28.string(),
8655
+ errorCode: z28.enum([
7112
8656
  "EMPTY_MESSAGE",
7113
8657
  "NO_FILES",
7114
8658
  "PROJECT_NOT_FOUND",
@@ -7121,36 +8665,37 @@ var CommitAndPushResultErrorSchema = z26.object({
7121
8665
  "NETWORK_ERROR",
7122
8666
  "TIMEOUT"
7123
8667
  ]),
7124
- details: z26.string().optional()
8668
+ details: z28.string().optional()
7125
8669
  });
7126
- var CommitAndPushResultSchema = z26.discriminatedUnion("success", [
8670
+ var CommitAndPushResultSchema = z28.discriminatedUnion("success", [
7127
8671
  CommitAndPushResultSuccessSchema,
7128
8672
  CommitAndPushResultErrorSchema
7129
8673
  ]);
7130
8674
 
7131
8675
  // src/server/lib/config/config.ts
7132
- import z28 from "zod";
8676
+ import z30 from "zod";
7133
8677
 
7134
8678
  // src/lib/i18n/schema.ts
7135
- import z27 from "zod";
7136
- var localeSchema = z27.enum(["ja", "en", "zh_CN"]);
8679
+ import z29 from "zod";
8680
+ var localeSchema = z29.enum(["ja", "en", "zh_CN"]);
7137
8681
 
7138
8682
  // src/server/lib/config/config.ts
7139
- var userConfigSchema = z28.object({
7140
- hideNoUserMessageSession: z28.boolean().optional().default(true),
7141
- unifySameTitleSession: z28.boolean().optional().default(false),
7142
- enterKeyBehavior: z28.enum(["shift-enter-send", "enter-send", "command-enter-send"]).optional().default("shift-enter-send"),
7143
- permissionMode: z28.enum(["acceptEdits", "bypassPermissions", "default", "plan"]).optional().default("default"),
8683
+ var userConfigSchema = z30.object({
8684
+ hideNoUserMessageSession: z30.boolean().optional().default(true),
8685
+ unifySameTitleSession: z30.boolean().optional().default(false),
8686
+ enterKeyBehavior: z30.enum(["shift-enter-send", "enter-send", "command-enter-send"]).optional().default("shift-enter-send"),
8687
+ permissionMode: z30.enum(["acceptEdits", "bypassPermissions", "default", "plan"]).optional().default("default"),
7144
8688
  locale: localeSchema.optional().default("en"),
7145
- theme: z28.enum(["light", "dark", "system"]).optional().default("system"),
7146
- searchHotkey: z28.enum(["ctrl-k", "command-k"]).optional().default("command-k")
8689
+ theme: z30.enum(["light", "dark", "system"]).optional().default("system"),
8690
+ searchHotkey: z30.enum(["ctrl-k", "command-k"]).optional().default("command-k"),
8691
+ autoScheduleContinueOnRateLimit: z30.boolean().optional().default(false)
7147
8692
  });
7148
8693
  var defaultUserConfig = userConfigSchema.parse({});
7149
8694
 
7150
8695
  // src/server/lib/effect/toEffectResponse.ts
7151
- import { Effect as Effect46 } from "effect";
8696
+ import { Effect as Effect50 } from "effect";
7152
8697
  var effectToResponse = async (ctx, effect) => {
7153
- const result = await Effect46.runPromise(effect);
8698
+ const result = await Effect50.runPromise(effect);
7154
8699
  const result2 = ctx.json(result.response, result.status);
7155
8700
  return result2;
7156
8701
  };
@@ -7193,7 +8738,7 @@ var configMiddleware = createMiddleware2(
7193
8738
  );
7194
8739
 
7195
8740
  // src/server/hono/route.ts
7196
- var routes = (app, options) => Effect47.gen(function* () {
8741
+ var routes = (app, options) => Effect51.gen(function* () {
7197
8742
  const ccvOptionsService = yield* CcvOptionsService;
7198
8743
  yield* ccvOptionsService.loadCliOptions(options);
7199
8744
  const envService = yield* EnvService;
@@ -7212,9 +8757,10 @@ var routes = (app, options) => Effect47.gen(function* () {
7212
8757
  const schedulerController = yield* SchedulerController;
7213
8758
  const featureFlagController = yield* FeatureFlagController;
7214
8759
  const searchController = yield* SearchController;
8760
+ const tasksController = yield* TasksController;
7215
8761
  const authMiddlewareService = yield* AuthMiddleware;
7216
8762
  const { authMiddleware, validSessionToken, authEnabled, anthPassword } = yield* authMiddlewareService;
7217
- const runtime = yield* Effect47.runtime();
8763
+ const runtime = yield* Effect51.runtime();
7218
8764
  if ((yield* envService.getEnv("NEXT_PHASE")) !== "phase-production-build") {
7219
8765
  yield* initializeService.startInitialization();
7220
8766
  prexit(async () => {
@@ -7222,7 +8768,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7222
8768
  });
7223
8769
  }
7224
8770
  return app.use(configMiddleware).use(authMiddleware).use(async (c, next) => {
7225
- await Effect47.runPromise(
8771
+ await Effect51.runPromise(
7226
8772
  userConfigService.setUserConfig({
7227
8773
  ...c.get("userConfig")
7228
8774
  })
@@ -7230,7 +8776,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7230
8776
  await next();
7231
8777
  }).post(
7232
8778
  "/api/auth/login",
7233
- zValidator("json", z29.object({ password: z29.string() })),
8779
+ zValidator("json", z31.object({ password: z31.string() })),
7234
8780
  async (c) => {
7235
8781
  const { password } = c.req.valid("json");
7236
8782
  if (!authEnabled) {
@@ -7284,14 +8830,14 @@ var routes = (app, options) => Effect47.gen(function* () {
7284
8830
  return response;
7285
8831
  }).get(
7286
8832
  "/api/projects/:projectId",
7287
- zValidator("query", z29.object({ cursor: z29.string().optional() })),
8833
+ zValidator("query", z31.object({ cursor: z31.string().optional() })),
7288
8834
  async (c) => {
7289
8835
  const response = await effectToResponse(
7290
8836
  c,
7291
8837
  projectController.getProject({
7292
8838
  ...c.req.param(),
7293
8839
  ...c.req.valid("query")
7294
- }).pipe(Effect47.provide(runtime))
8840
+ }).pipe(Effect51.provide(runtime))
7295
8841
  );
7296
8842
  return response;
7297
8843
  }
@@ -7299,8 +8845,8 @@ var routes = (app, options) => Effect47.gen(function* () {
7299
8845
  "/api/projects",
7300
8846
  zValidator(
7301
8847
  "json",
7302
- z29.object({
7303
- projectPath: z29.string().min(1, "Project path is required")
8848
+ z31.object({
8849
+ projectPath: z31.string().min(1, "Project path is required")
7304
8850
  })
7305
8851
  ),
7306
8852
  async (c) => {
@@ -7308,7 +8854,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7308
8854
  c,
7309
8855
  projectController.createProject({
7310
8856
  ...c.req.valid("json")
7311
- }).pipe(Effect47.provide(runtime))
8857
+ }).pipe(Effect51.provide(runtime))
7312
8858
  );
7313
8859
  return response;
7314
8860
  }
@@ -7317,13 +8863,13 @@ var routes = (app, options) => Effect47.gen(function* () {
7317
8863
  c,
7318
8864
  projectController.getProjectLatestSession({
7319
8865
  ...c.req.param()
7320
- }).pipe(Effect47.provide(runtime))
8866
+ }).pipe(Effect51.provide(runtime))
7321
8867
  );
7322
8868
  return response;
7323
8869
  }).get("/api/projects/:projectId/sessions/:sessionId", async (c) => {
7324
8870
  const response = await effectToResponse(
7325
8871
  c,
7326
- sessionController.getSession({ ...c.req.param() }).pipe(Effect47.provide(runtime))
8872
+ sessionController.getSession({ ...c.req.param() }).pipe(Effect51.provide(runtime))
7327
8873
  );
7328
8874
  return response;
7329
8875
  }).get(
@@ -7331,19 +8877,19 @@ var routes = (app, options) => Effect47.gen(function* () {
7331
8877
  async (c) => {
7332
8878
  const response = await effectToResponse(
7333
8879
  c,
7334
- sessionController.exportSessionHtml({ ...c.req.param() }).pipe(Effect47.provide(runtime))
8880
+ sessionController.exportSessionHtml({ ...c.req.param() }).pipe(Effect51.provide(runtime))
7335
8881
  );
7336
8882
  return response;
7337
8883
  }
7338
8884
  ).delete("/api/projects/:projectId/sessions/:sessionId", async (c) => {
7339
8885
  const response = await effectToResponse(
7340
8886
  c,
7341
- sessionController.deleteSession({ ...c.req.param() }).pipe(Effect47.provide(runtime))
8887
+ sessionController.deleteSession({ ...c.req.param() }).pipe(Effect51.provide(runtime))
7342
8888
  );
7343
8889
  return response;
7344
8890
  }).get(
7345
8891
  "/api/projects/:projectId/agent-sessions/:agentId",
7346
- zValidator("query", z29.object({ sessionId: z29.string().optional() })),
8892
+ zValidator("query", z31.object({ sessionId: z31.string().optional() })),
7347
8893
  async (c) => {
7348
8894
  const { projectId, agentId } = c.req.param();
7349
8895
  const { sessionId } = c.req.valid("query");
@@ -7353,7 +8899,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7353
8899
  projectId,
7354
8900
  agentId,
7355
8901
  sessionId
7356
- }).pipe(Effect47.provide(runtime))
8902
+ }).pipe(Effect51.provide(runtime))
7357
8903
  );
7358
8904
  return response;
7359
8905
  }
@@ -7362,16 +8908,16 @@ var routes = (app, options) => Effect47.gen(function* () {
7362
8908
  c,
7363
8909
  gitController.getCurrentRevisions({
7364
8910
  ...c.req.param()
7365
- }).pipe(Effect47.provide(runtime))
8911
+ }).pipe(Effect51.provide(runtime))
7366
8912
  );
7367
8913
  return response;
7368
8914
  }).post(
7369
8915
  "/api/projects/:projectId/git/diff",
7370
8916
  zValidator(
7371
8917
  "json",
7372
- z29.object({
7373
- fromRef: z29.string().min(1, "fromRef is required"),
7374
- toRef: z29.string().min(1, "toRef is required")
8918
+ z31.object({
8919
+ fromRef: z31.string().min(1, "fromRef is required"),
8920
+ toRef: z31.string().min(1, "toRef is required")
7375
8921
  })
7376
8922
  ),
7377
8923
  async (c) => {
@@ -7380,7 +8926,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7380
8926
  gitController.getGitDiff({
7381
8927
  ...c.req.param(),
7382
8928
  ...c.req.valid("json")
7383
- }).pipe(Effect47.provide(runtime))
8929
+ }).pipe(Effect51.provide(runtime))
7384
8930
  );
7385
8931
  return response;
7386
8932
  }
@@ -7393,7 +8939,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7393
8939
  gitController.commitFiles({
7394
8940
  ...c.req.param(),
7395
8941
  ...c.req.valid("json")
7396
- }).pipe(Effect47.provide(runtime))
8942
+ }).pipe(Effect51.provide(runtime))
7397
8943
  );
7398
8944
  return response;
7399
8945
  }
@@ -7406,7 +8952,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7406
8952
  gitController.pushCommits({
7407
8953
  ...c.req.param(),
7408
8954
  ...c.req.valid("json")
7409
- }).pipe(Effect47.provide(runtime))
8955
+ }).pipe(Effect51.provide(runtime))
7410
8956
  );
7411
8957
  return response;
7412
8958
  }
@@ -7419,7 +8965,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7419
8965
  gitController.commitAndPush({
7420
8966
  ...c.req.param(),
7421
8967
  ...c.req.valid("json")
7422
- }).pipe(Effect47.provide(runtime))
8968
+ }).pipe(Effect51.provide(runtime))
7423
8969
  );
7424
8970
  return response;
7425
8971
  }
@@ -7428,7 +8974,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7428
8974
  c,
7429
8975
  claudeCodeController.getClaudeCommands({
7430
8976
  ...c.req.param()
7431
- }).pipe(Effect47.provide(runtime))
8977
+ }).pipe(Effect51.provide(runtime))
7432
8978
  );
7433
8979
  return response;
7434
8980
  }).get("/api/projects/:projectId/mcp/list", async (c) => {
@@ -7436,19 +8982,19 @@ var routes = (app, options) => Effect47.gen(function* () {
7436
8982
  c,
7437
8983
  claudeCodeController.getMcpListRoute({
7438
8984
  ...c.req.param()
7439
- }).pipe(Effect47.provide(runtime))
8985
+ }).pipe(Effect51.provide(runtime))
7440
8986
  );
7441
8987
  return response;
7442
8988
  }).get("/api/cc/meta", async (c) => {
7443
8989
  const response = await effectToResponse(
7444
8990
  c,
7445
- claudeCodeController.getClaudeCodeMeta().pipe(Effect47.provide(runtime))
8991
+ claudeCodeController.getClaudeCodeMeta().pipe(Effect51.provide(runtime))
7446
8992
  );
7447
8993
  return response;
7448
8994
  }).get("/api/cc/features", async (c) => {
7449
8995
  const response = await effectToResponse(
7450
8996
  c,
7451
- claudeCodeController.getAvailableFeatures().pipe(Effect47.provide(runtime))
8997
+ claudeCodeController.getAvailableFeatures().pipe(Effect51.provide(runtime))
7452
8998
  );
7453
8999
  return response;
7454
9000
  }).get("/api/cc/session-processes", async (c) => {
@@ -7461,10 +9007,10 @@ var routes = (app, options) => Effect47.gen(function* () {
7461
9007
  "/api/cc/session-processes",
7462
9008
  zValidator(
7463
9009
  "json",
7464
- z29.object({
7465
- projectId: z29.string(),
9010
+ z31.object({
9011
+ projectId: z31.string(),
7466
9012
  input: userMessageInputSchema,
7467
- baseSessionId: z29.string().optional()
9013
+ baseSessionId: z31.string().optional()
7468
9014
  })
7469
9015
  ),
7470
9016
  async (c) => {
@@ -7480,10 +9026,10 @@ var routes = (app, options) => Effect47.gen(function* () {
7480
9026
  "/api/cc/session-processes/:sessionProcessId/continue",
7481
9027
  zValidator(
7482
9028
  "json",
7483
- z29.object({
7484
- projectId: z29.string(),
9029
+ z31.object({
9030
+ projectId: z31.string(),
7485
9031
  input: userMessageInputSchema,
7486
- baseSessionId: z29.string()
9032
+ baseSessionId: z31.string()
7487
9033
  })
7488
9034
  ),
7489
9035
  async (c) => {
@@ -7492,16 +9038,16 @@ var routes = (app, options) => Effect47.gen(function* () {
7492
9038
  claudeCodeSessionProcessController.continueSessionProcess({
7493
9039
  ...c.req.param(),
7494
9040
  ...c.req.valid("json")
7495
- }).pipe(Effect47.provide(runtime))
9041
+ }).pipe(Effect51.provide(runtime))
7496
9042
  );
7497
9043
  return response;
7498
9044
  }
7499
9045
  ).post(
7500
9046
  "/api/cc/session-processes/:sessionProcessId/abort",
7501
- zValidator("json", z29.object({ projectId: z29.string() })),
9047
+ zValidator("json", z31.object({ projectId: z31.string() })),
7502
9048
  async (c) => {
7503
9049
  const { sessionProcessId } = c.req.param();
7504
- void Effect47.runFork(
9050
+ void Effect51.runFork(
7505
9051
  claudeCodeLifeCycleService.abortTask(sessionProcessId)
7506
9052
  );
7507
9053
  return c.json({ message: "Task aborted" });
@@ -7510,9 +9056,9 @@ var routes = (app, options) => Effect47.gen(function* () {
7510
9056
  "/api/cc/permission-response",
7511
9057
  zValidator(
7512
9058
  "json",
7513
- z29.object({
7514
- permissionRequestId: z29.string(),
7515
- decision: z29.enum(["allow", "deny"])
9059
+ z31.object({
9060
+ permissionRequestId: z31.string(),
9061
+ decision: z31.enum(["allow", "deny"])
7516
9062
  })
7517
9063
  ),
7518
9064
  async (c) => {
@@ -7529,7 +9075,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7529
9075
  c,
7530
9076
  async (rawStream) => {
7531
9077
  await Runtime3.runPromise(runtime)(
7532
- sseController.handleSSE(rawStream).pipe(Effect47.provide(TypeSafeSSE.make(rawStream)))
9078
+ sseController.handleSSE(rawStream).pipe(Effect51.provide(TypeSafeSSE.make(rawStream)))
7533
9079
  );
7534
9080
  },
7535
9081
  async (err) => {
@@ -7539,7 +9085,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7539
9085
  }).get("/api/scheduler/jobs", async (c) => {
7540
9086
  const response = await effectToResponse(
7541
9087
  c,
7542
- schedulerController.getJobs().pipe(Effect47.provide(runtime))
9088
+ schedulerController.getJobs().pipe(Effect51.provide(runtime))
7543
9089
  );
7544
9090
  return response;
7545
9091
  }).post(
@@ -7550,7 +9096,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7550
9096
  c,
7551
9097
  schedulerController.addJob({
7552
9098
  job: c.req.valid("json")
7553
- }).pipe(Effect47.provide(runtime))
9099
+ }).pipe(Effect51.provide(runtime))
7554
9100
  );
7555
9101
  return response;
7556
9102
  }
@@ -7563,7 +9109,7 @@ var routes = (app, options) => Effect47.gen(function* () {
7563
9109
  schedulerController.updateJob({
7564
9110
  id: c.req.param("id"),
7565
9111
  job: c.req.valid("json")
7566
- }).pipe(Effect47.provide(runtime))
9112
+ }).pipe(Effect51.provide(runtime))
7567
9113
  );
7568
9114
  return response;
7569
9115
  }
@@ -7572,16 +9118,16 @@ var routes = (app, options) => Effect47.gen(function* () {
7572
9118
  c,
7573
9119
  schedulerController.deleteJob({
7574
9120
  id: c.req.param("id")
7575
- }).pipe(Effect47.provide(runtime))
9121
+ }).pipe(Effect51.provide(runtime))
7576
9122
  );
7577
9123
  return response;
7578
9124
  }).get(
7579
9125
  "/api/fs/file-completion",
7580
9126
  zValidator(
7581
9127
  "query",
7582
- z29.object({
7583
- projectId: z29.string(),
7584
- basePath: z29.string().optional().default("/api/")
9128
+ z31.object({
9129
+ projectId: z31.string(),
9130
+ basePath: z31.string().optional().default("/api/")
7585
9131
  })
7586
9132
  ),
7587
9133
  async (c) => {
@@ -7597,9 +9143,9 @@ var routes = (app, options) => Effect47.gen(function* () {
7597
9143
  "/api/fs/directory-browser",
7598
9144
  zValidator(
7599
9145
  "query",
7600
- z29.object({
7601
- currentPath: z29.string().optional(),
7602
- showHidden: z29.string().optional().transform((val) => val === "true")
9146
+ z31.object({
9147
+ currentPath: z31.string().optional(),
9148
+ showHidden: z31.string().optional().transform((val) => val === "true")
7603
9149
  })
7604
9150
  ),
7605
9151
  async (c) => {
@@ -7615,42 +9161,119 @@ var routes = (app, options) => Effect47.gen(function* () {
7615
9161
  "/api/search",
7616
9162
  zValidator(
7617
9163
  "query",
7618
- z29.object({
7619
- q: z29.string().min(2),
7620
- limit: z29.string().optional().transform((val) => val ? parseInt(val, 10) : void 0),
7621
- projectId: z29.string().optional()
9164
+ z31.object({
9165
+ q: z31.string().min(2),
9166
+ limit: z31.string().optional().transform((val) => val ? parseInt(val, 10) : void 0),
9167
+ projectId: z31.string().optional()
7622
9168
  })
7623
9169
  ),
7624
9170
  async (c) => {
7625
9171
  const { q, limit, projectId } = c.req.valid("query");
7626
9172
  const response = await effectToResponse(
7627
9173
  c,
7628
- searchController.search({ query: q, limit, projectId }).pipe(Effect47.provide(runtime))
9174
+ searchController.search({ query: q, limit, projectId }).pipe(Effect51.provide(runtime))
7629
9175
  );
7630
9176
  return response;
7631
9177
  }
7632
9178
  ).get("/api/flags", async (c) => {
7633
9179
  const response = await effectToResponse(
7634
9180
  c,
7635
- featureFlagController.getFlags().pipe(Effect47.provide(runtime))
9181
+ featureFlagController.getFlags().pipe(Effect51.provide(runtime))
7636
9182
  );
7637
9183
  return response;
7638
- });
9184
+ }).get(
9185
+ "/api/tasks",
9186
+ zValidator(
9187
+ "query",
9188
+ z31.object({
9189
+ projectId: z31.string(),
9190
+ sessionId: z31.string().optional()
9191
+ })
9192
+ ),
9193
+ async (c) => {
9194
+ const { projectId, sessionId } = c.req.valid("query");
9195
+ const projectPath = decodeProjectId(projectId);
9196
+ const response = await effectToResponse(
9197
+ c,
9198
+ tasksController.listTasks(projectPath, sessionId).pipe(
9199
+ Effect51.map((tasks) => ({
9200
+ status: 200,
9201
+ response: tasks
9202
+ })),
9203
+ Effect51.provide(runtime)
9204
+ )
9205
+ );
9206
+ return response;
9207
+ }
9208
+ ).post(
9209
+ "/api/tasks",
9210
+ zValidator(
9211
+ "query",
9212
+ z31.object({
9213
+ projectId: z31.string(),
9214
+ sessionId: z31.string().optional()
9215
+ })
9216
+ ),
9217
+ zValidator("json", TaskCreateSchema),
9218
+ async (c) => {
9219
+ const { projectId, sessionId } = c.req.valid("query");
9220
+ const body = c.req.valid("json");
9221
+ const projectPath = decodeProjectId(projectId);
9222
+ const response = await effectToResponse(
9223
+ c,
9224
+ tasksController.createTask(projectPath, body, sessionId).pipe(
9225
+ Effect51.map((task) => ({
9226
+ status: 200,
9227
+ response: task
9228
+ })),
9229
+ Effect51.provide(runtime)
9230
+ )
9231
+ );
9232
+ return response;
9233
+ }
9234
+ ).patch(
9235
+ "/api/tasks/:id",
9236
+ zValidator(
9237
+ "query",
9238
+ z31.object({
9239
+ projectId: z31.string(),
9240
+ sessionId: z31.string().optional()
9241
+ })
9242
+ ),
9243
+ zValidator("json", TaskUpdateSchema.omit({ taskId: true })),
9244
+ async (c) => {
9245
+ const { id } = c.req.param();
9246
+ const { projectId, sessionId } = c.req.valid("query");
9247
+ const body = c.req.valid("json");
9248
+ const projectPath = decodeProjectId(projectId);
9249
+ const response = await effectToResponse(
9250
+ c,
9251
+ tasksController.updateTask(projectPath, { ...body, taskId: id }, sessionId).pipe(
9252
+ Effect51.map((task) => ({
9253
+ status: 200,
9254
+ response: task
9255
+ })),
9256
+ Effect51.provide(runtime)
9257
+ )
9258
+ );
9259
+ return response;
9260
+ }
9261
+ );
7639
9262
  });
7640
9263
 
7641
9264
  // src/server/lib/effect/layers.ts
7642
9265
  import { NodeContext } from "@effect/platform-node";
7643
- import { Layer as Layer39 } from "effect";
7644
- var platformLayer = Layer39.mergeAll(
9266
+ import { Layer as Layer42 } from "effect";
9267
+ var platformLayer = Layer42.mergeAll(
7645
9268
  ApplicationContext.Live,
7646
9269
  UserConfigService.Live,
7647
9270
  EventBus.Live,
7648
9271
  EnvService.Live,
7649
9272
  CcvOptionsService.Live
7650
9273
  ).pipe(
7651
- Layer39.provide(EnvService.Live),
7652
- Layer39.provide(CcvOptionsService.Live),
7653
- Layer39.provide(NodeContext.layer)
9274
+ Layer42.provide(EnvService.Live),
9275
+ Layer42.provide(CcvOptionsService.Live),
9276
+ Layer42.provide(NodeContext.layer)
7654
9277
  );
7655
9278
 
7656
9279
  // src/server/startServer.ts
@@ -7673,49 +9296,8 @@ var startServer = async (options) => {
7673
9296
  return c.html(html);
7674
9297
  });
7675
9298
  }
7676
- const program2 = routes(honoApp, options).pipe(
7677
- /** Presentation */
7678
- Effect48.provide(ProjectController.Live),
7679
- Effect48.provide(SessionController.Live),
7680
- Effect48.provide(AgentSessionController.Live),
7681
- Effect48.provide(GitController.Live),
7682
- Effect48.provide(ClaudeCodeController.Live),
7683
- Effect48.provide(ClaudeCodeSessionProcessController.Live),
7684
- Effect48.provide(ClaudeCodePermissionController.Live),
7685
- Effect48.provide(FileSystemController.Live),
7686
- Effect48.provide(SSEController.Live),
7687
- Effect48.provide(SchedulerController.Live),
7688
- Effect48.provide(FeatureFlagController.Live),
7689
- Effect48.provide(SearchController.Live)
7690
- ).pipe(
7691
- /** Application */
7692
- Effect48.provide(InitializeService.Live),
7693
- Effect48.provide(FileWatcherService.Live),
7694
- Effect48.provide(AuthMiddleware.Live)
7695
- ).pipe(
7696
- /** Domain */
7697
- Effect48.provide(ClaudeCodeLifeCycleService.Live),
7698
- Effect48.provide(ClaudeCodePermissionService.Live),
7699
- Effect48.provide(ClaudeCodeSessionProcessService.Live),
7700
- Effect48.provide(ClaudeCodeService.Live),
7701
- Effect48.provide(GitService.Live),
7702
- Effect48.provide(SchedulerService.Live),
7703
- Effect48.provide(SchedulerConfigBaseDir.Live),
7704
- Effect48.provide(SearchService.Live)
7705
- ).pipe(
7706
- /** Infrastructure */
7707
- Effect48.provide(ProjectRepository.Live),
7708
- Effect48.provide(SessionRepository.Live),
7709
- Effect48.provide(ProjectMetaService.Live),
7710
- Effect48.provide(SessionMetaService.Live),
7711
- Effect48.provide(VirtualConversationDatabase.Live),
7712
- Effect48.provide(AgentSessionLayer)
7713
- ).pipe(
7714
- /** Platform */
7715
- Effect48.provide(platformLayer),
7716
- Effect48.provide(NodeContext2.layer)
7717
- );
7718
- await Effect48.runPromise(program2);
9299
+ const program2 = routes(honoApp, options).pipe(Effect52.provide(MainLayer));
9300
+ await Effect52.runPromise(program2);
7719
9301
  const port = isDevelopment ? (
7720
9302
  // biome-ignore lint/style/noProcessEnv: allow only here
7721
9303
  process.env.DEV_BE_PORT ?? "3401"
@@ -7735,12 +9317,65 @@ var startServer = async (options) => {
7735
9317
  }
7736
9318
  );
7737
9319
  };
9320
+ var PlatformLayer = Layer43.mergeAll(platformLayer, NodeContext2.layer);
9321
+ var InfraBasics = Layer43.mergeAll(
9322
+ VirtualConversationDatabase.Live,
9323
+ ProjectMetaService.Live,
9324
+ SessionMetaService.Live
9325
+ );
9326
+ var InfraRepos = Layer43.mergeAll(
9327
+ ProjectRepository.Live,
9328
+ SessionRepository.Live
9329
+ ).pipe(Layer43.provideMerge(InfraBasics));
9330
+ var InfraLayer = AgentSessionLayer.pipe(Layer43.provideMerge(InfraRepos));
9331
+ var DomainBase = Layer43.mergeAll(
9332
+ ClaudeCodePermissionService.Live,
9333
+ ClaudeCodeSessionProcessService.Live,
9334
+ ClaudeCodeService.Live,
9335
+ GitService.Live,
9336
+ SchedulerService.Live,
9337
+ SchedulerConfigBaseDir.Live,
9338
+ SearchService.Live,
9339
+ TasksService.Live
9340
+ );
9341
+ var DomainLayer = ClaudeCodeLifeCycleService.Live.pipe(
9342
+ Layer43.provideMerge(DomainBase)
9343
+ );
9344
+ var AppServices = Layer43.mergeAll(
9345
+ FileWatcherService.Live,
9346
+ RateLimitAutoScheduleService.Live,
9347
+ AuthMiddleware.Live
9348
+ );
9349
+ var ApplicationLayer = InitializeService.Live.pipe(
9350
+ Layer43.provideMerge(AppServices)
9351
+ );
9352
+ var PresentationLayer = Layer43.mergeAll(
9353
+ ProjectController.Live,
9354
+ SessionController.Live,
9355
+ AgentSessionController.Live,
9356
+ GitController.Live,
9357
+ ClaudeCodeController.Live,
9358
+ ClaudeCodeSessionProcessController.Live,
9359
+ ClaudeCodePermissionController.Live,
9360
+ FileSystemController.Live,
9361
+ SSEController.Live,
9362
+ SchedulerController.Live,
9363
+ FeatureFlagController.Live,
9364
+ SearchController.Live,
9365
+ TasksController.Live
9366
+ );
9367
+ var MainLayer = PresentationLayer.pipe(
9368
+ Layer43.provideMerge(ApplicationLayer),
9369
+ Layer43.provideMerge(DomainLayer),
9370
+ Layer43.provideMerge(InfraLayer),
9371
+ Layer43.provideMerge(PlatformLayer)
9372
+ );
7738
9373
 
7739
9374
  // src/server/main.ts
7740
9375
  var program = new Command3();
7741
9376
  program.name(package_default.name).version(package_default.version).description(package_default.description);
7742
9377
  program.option("-p, --port <port>", "port to listen on").option("-h, --hostname <hostname>", "hostname to listen on").option("-P, --password <password>", "password to authenticate").option("-e, --executable <executable>", "path to claude code executable").option("--claude-dir <claude-dir>", "path to claude directory").action(async (options) => {
7743
- await Effect49.runPromise(checkDeprecatedEnvs);
9378
+ await Effect53.runPromise(checkDeprecatedEnvs);
7744
9379
  await startServer(options);
7745
9380
  });
7746
9381
  var main = async () => {