tarsk 0.5.43 → 0.5.44

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.
Files changed (78) hide show
  1. package/dist/index.js +318 -144
  2. package/dist/public/assets/{account-view-xKotpUyx.js → account-view-CTizS7Yb.js} +1 -1
  3. package/dist/public/assets/{api-D6uLdHBQ.js → api-Cuj9iSVv.js} +1 -1
  4. package/dist/public/assets/{browser-tab-DxigYzoT.js → browser-tab-DSpOIu84.js} +1 -1
  5. package/dist/public/assets/commit-dialog-kTaKOsNO.js +1 -0
  6. package/dist/public/assets/{context-menu-rC7iWcty.js → context-menu-DRXV-Suu.js} +1 -1
  7. package/dist/public/assets/create-repo-dialog-gIZLk6LZ.js +1 -0
  8. package/dist/public/assets/dialogs-config-CxRnlUcQ.js +51 -0
  9. package/dist/public/assets/diff-view-B2OdJMRe.js +3 -0
  10. package/dist/public/assets/explorer-tab-view-fejcb7N1.js +2 -0
  11. package/dist/public/assets/{explorer-tree-BC4fBpxi.js → explorer-tree-C2kLAwI0.js} +1 -1
  12. package/dist/public/assets/{explorer-view-DIM08sdy.js → explorer-view-BmKKHyrH.js} +1 -1
  13. package/dist/public/assets/git-history-dialog-B2ABiCXZ.js +1 -0
  14. package/dist/public/assets/git-ops-button-Bxx_jyqR.js +2 -0
  15. package/dist/public/assets/history-view-DebwP9D7.js +9 -0
  16. package/dist/public/assets/{index-DKOXV50p.css → index-C2_gO3dh.css} +1 -1
  17. package/dist/public/assets/index-D9aY24hf.js +89 -0
  18. package/dist/public/assets/{mcp-server-card-Cy4RU2_Q.js → mcp-server-card-B6iwOybN.js} +1 -1
  19. package/dist/public/assets/merged-pr-dialog-CObeSEaB.js +1 -0
  20. package/dist/public/assets/{model-star-rating-BmkpdXfr.js → model-star-rating-BgdOyTTO.js} +1 -1
  21. package/dist/public/assets/onboarding-BN_K08ma.js +1 -0
  22. package/dist/public/assets/project-settings-view-BDvHOmF8.js +1 -0
  23. package/dist/public/assets/providers-list-view-DydWz8z3.js +1 -0
  24. package/dist/public/assets/pull-request-dialog-LJhZpT8C.js +1 -0
  25. package/dist/public/assets/pull-with-changes-dialog-BPIj9wIk.js +1 -0
  26. package/dist/public/assets/push-before-pr-dialog-DzBp93KT.js +1 -0
  27. package/dist/public/assets/{radio-group-CbatNaj1.js → radio-group-B9xMCZ1X.js} +1 -1
  28. package/dist/public/assets/react-vendor-CE5Dmjd0.js +16 -0
  29. package/dist/public/assets/settings-general-view-D_44BPVr.js +1 -0
  30. package/dist/public/assets/{settings-instructions-view-DMAjbi6E.js → settings-instructions-view-Y6EK67-G.js} +1 -1
  31. package/dist/public/assets/{settings-list-B8hiBkBz.js → settings-list-BZDwK9bE.js} +1 -1
  32. package/dist/public/assets/settings-mcp-servers-view-B8IqOOnN.js +5 -0
  33. package/dist/public/assets/{settings-models-skeleton-DPnYbg69.js → settings-models-skeleton-DAEnT4kC.js} +1 -1
  34. package/dist/public/assets/settings-models-view-CluPgRkw.js +1 -0
  35. package/dist/public/assets/{settings-rules-view-DBk7DzV2.js → settings-rules-view-CxHrCDwn.js} +3 -3
  36. package/dist/public/assets/settings-skills-view-BE9u6FvJ.js +2 -0
  37. package/dist/public/assets/settings-slash-commands-view-CVOZ3GH3.js +1 -0
  38. package/dist/public/assets/{settings-subagents-view-D98Nxoly.js → settings-subagents-view-mguSf6zE.js} +2 -2
  39. package/dist/public/assets/settings-system-prompt-view-0sXVxXh3.js +1 -0
  40. package/dist/public/assets/settings-view-BE8EW7s7.js +2 -0
  41. package/dist/public/assets/{skeleton-BHhGML7J.js → skeleton-CVhaOA8k.js} +1 -1
  42. package/dist/public/assets/{slug-utils-DyRUJ1NS.js → slug-utils-DGOwJkKI.js} +1 -1
  43. package/dist/public/assets/{terminal-panel-DTOx74_o.js → terminal-panel-PjkgqbY6.js} +1 -1
  44. package/dist/public/assets/{ui-components-Jc6oi6bz.js → ui-components-DaffuSG7.js} +1 -1
  45. package/dist/public/assets/{use-deferred-search-B7EdyRbt.js → use-deferred-search-b843Ztu9.js} +1 -1
  46. package/dist/public/assets/{utils-tgi5ym_d.js → utils-BIt9KCVx.js} +1 -1
  47. package/dist/public/assets/vosk-speech-CgILeNJR.js +2 -0
  48. package/dist/public/assets/{web-CUAWBWPy.js → web-BBGaE-6B.js} +1 -1
  49. package/dist/public/assets/{web-C3vJZ_3_.js → web-D6dBzWD1.js} +1 -1
  50. package/dist/public/index.html +8 -8
  51. package/package.json +1 -1
  52. package/dist/public/assets/commit-dialog-CLQM9ah3.js +0 -1
  53. package/dist/public/assets/create-repo-dialog-C6k5wZPW.js +0 -1
  54. package/dist/public/assets/dialogs-config-CjKh5Rl2.js +0 -51
  55. package/dist/public/assets/diff-view-DWDWI5nl.js +0 -3
  56. package/dist/public/assets/explorer-tab-view-B0kT8Hl6.js +0 -2
  57. package/dist/public/assets/git-history-dialog-CuxOTngT.js +0 -1
  58. package/dist/public/assets/git-ops-button-C04zFAnF.js +0 -2
  59. package/dist/public/assets/history-view-ar7GLZ-R.js +0 -9
  60. package/dist/public/assets/index--HY4BbcM.js +0 -90
  61. package/dist/public/assets/merged-pr-dialog-Bo07VouF.js +0 -1
  62. package/dist/public/assets/onboarding-ClZrOxX7.js +0 -1
  63. package/dist/public/assets/project-settings-view-Dm9pQAp_.js +0 -1
  64. package/dist/public/assets/providers-list-view-D5gHsjl_.js +0 -1
  65. package/dist/public/assets/pull-request-dialog-8AYlOUNX.js +0 -1
  66. package/dist/public/assets/pull-with-changes-dialog-CSa5OE-d.js +0 -1
  67. package/dist/public/assets/push-before-pr-dialog-D5W_xsqv.js +0 -1
  68. package/dist/public/assets/react-vendor-DwQYi7es.js +0 -16
  69. package/dist/public/assets/settings-general-view-BP5ULy9A.js +0 -1
  70. package/dist/public/assets/settings-mcp-servers-view-OimQz-Rd.js +0 -5
  71. package/dist/public/assets/settings-models-view-Fq3WtdKG.js +0 -1
  72. package/dist/public/assets/settings-skills-view-CmOw-WMM.js +0 -2
  73. package/dist/public/assets/settings-slash-commands-view-FsrF5FkK.js +0 -1
  74. package/dist/public/assets/settings-system-prompt-view-B6Hy9ZyK.js +0 -1
  75. package/dist/public/assets/settings-view-J-rjoRcU.js +0 -2
  76. package/dist/public/assets/whisper-wasm-CWcbC1MB.js +0 -2
  77. package/dist/public/wasm/libmain-CWYJvMY5.js +0 -3318
  78. package/dist/public/wasm/libmain-D9-QM3iM.mjs +0 -3301
package/dist/index.js CHANGED
@@ -9080,12 +9080,87 @@ function stageAllChanges(gitRoot) {
9080
9080
  proc.on("error", reject);
9081
9081
  });
9082
9082
  }
9083
+ function getCommitCount(gitRoot) {
9084
+ return new Promise((resolve8, reject) => {
9085
+ const proc = spawnProcess("git", ["rev-list", "--count", "HEAD"], { cwd: gitRoot });
9086
+ let out = "";
9087
+ let err = "";
9088
+ if (proc.stdout) {
9089
+ proc.stdout.on("data", (d) => {
9090
+ out += d.toString();
9091
+ });
9092
+ }
9093
+ if (proc.stderr) {
9094
+ proc.stderr.on("data", (d) => {
9095
+ err += d.toString();
9096
+ });
9097
+ }
9098
+ proc.on("close", (code) => {
9099
+ if (code === 0) {
9100
+ resolve8(parseInt(out.trim(), 10) || 0);
9101
+ return;
9102
+ }
9103
+ if (/unknown revision|ambiguous argument|does not have any commits yet|bad revision|needed a single revision/i.test(
9104
+ err
9105
+ )) {
9106
+ resolve8(0);
9107
+ return;
9108
+ }
9109
+ reject(new Error(err.trim() || "Failed to count commits"));
9110
+ });
9111
+ proc.on("error", reject);
9112
+ });
9113
+ }
9114
+ function checkBranchExists(gitRoot, branchName) {
9115
+ return new Promise((resolve8) => {
9116
+ const proc = spawnProcess(
9117
+ "git",
9118
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
9119
+ {
9120
+ cwd: gitRoot
9121
+ }
9122
+ );
9123
+ proc.on("close", (code) => {
9124
+ resolve8(code === 0);
9125
+ });
9126
+ proc.on("error", () => {
9127
+ resolve8(false);
9128
+ });
9129
+ });
9130
+ }
9131
+ function renameCurrentBranch(gitRoot, newBranchName) {
9132
+ return new Promise((resolve8, reject) => {
9133
+ const proc = spawnProcess("git", ["branch", "-m", newBranchName], {
9134
+ cwd: gitRoot
9135
+ });
9136
+ let err = "";
9137
+ if (proc.stderr) {
9138
+ proc.stderr.on("data", (d) => {
9139
+ err += d.toString();
9140
+ });
9141
+ }
9142
+ proc.on("close", (code) => {
9143
+ if (code === 0) {
9144
+ resolve8();
9145
+ } else {
9146
+ reject(new Error(err || `Failed to rename branch to "${newBranchName}"`));
9147
+ }
9148
+ });
9149
+ proc.on("error", reject);
9150
+ });
9151
+ }
9083
9152
  function commitChanges(gitRoot, message) {
9084
9153
  return new Promise((resolve8, reject) => {
9085
9154
  const proc = spawnProcess("git", ["commit", "-m", message], { cwd: gitRoot });
9155
+ let err = "";
9156
+ if (proc.stderr) {
9157
+ proc.stderr.on("data", (d) => {
9158
+ err += d.toString();
9159
+ });
9160
+ }
9086
9161
  proc.on("close", (code) => {
9087
- if (code === 0) resolve8();
9088
- else reject(new Error("Failed to commit changes"));
9162
+ if (code === 0) resolve8({ createdCommit: true });
9163
+ else reject(new Error(err.trim() || "Failed to commit changes"));
9089
9164
  });
9090
9165
  proc.on("error", reject);
9091
9166
  });
@@ -14661,7 +14736,8 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
14661
14736
  attachments,
14662
14737
  planMode,
14663
14738
  ralphMode,
14664
- checkpointRef
14739
+ checkpointRef,
14740
+ conversationId: requestedConversationId
14665
14741
  } = body;
14666
14742
  let model = baseModel;
14667
14743
  if (provider && typeof provider === "string") {
@@ -14758,7 +14834,7 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
14758
14834
  }
14759
14835
  return streamAsyncGenerator(c, replayGenerator());
14760
14836
  }
14761
- let conversationId = thread.currentConversationId;
14837
+ let conversationId = typeof requestedConversationId === "string" && requestedConversationId.length > 0 ? requestedConversationId : thread.currentConversationId;
14762
14838
  if (!conversationId) {
14763
14839
  conversationId = randomUUID8();
14764
14840
  const db2 = await getDatabase();
@@ -19449,7 +19525,8 @@ import { rm as rm4 } from "fs/promises";
19449
19525
  // src/agent/agent.process-manager.ts
19450
19526
  init_utils();
19451
19527
  import { EventEmitter } from "events";
19452
- init_tarsk_debug();
19528
+
19529
+ // src/core/dev-server-url-detector.ts
19453
19530
  var URL_PATTERNS = [
19454
19531
  // http(s)://localhost:PORT
19455
19532
  /https?:\/\/localhost[:/](\d+)/i,
@@ -19462,6 +19539,24 @@ var URL_PATTERNS = [
19462
19539
  // Local address patterns like http://192.168...
19463
19540
  /https?:\/\/(192\.168\.\d+\.\d+):(\d+)/i
19464
19541
  ];
19542
+ function detectDevServerUrl(text) {
19543
+ for (const pattern of URL_PATTERNS) {
19544
+ const match = text.match(pattern);
19545
+ if (match) {
19546
+ if (match[1] && !match[0].includes("://")) {
19547
+ if (match[0].startsWith("127.0.0.1:")) {
19548
+ return `http://${match[0]}`;
19549
+ }
19550
+ return `http://localhost:${match[1]}`;
19551
+ }
19552
+ return match[0];
19553
+ }
19554
+ }
19555
+ return void 0;
19556
+ }
19557
+
19558
+ // src/agent/agent.process-manager.ts
19559
+ init_tarsk_debug();
19465
19560
  var ProcessManager = class extends EventEmitter {
19466
19561
  processes = /* @__PURE__ */ new Map();
19467
19562
  detectedUrls = /* @__PURE__ */ new Map();
@@ -19537,7 +19632,7 @@ var ProcessManager = class extends EventEmitter {
19537
19632
  const text = data.toString();
19538
19633
  urlBuffer += text;
19539
19634
  if (!urlDetected) {
19540
- const detectedUrl = this.detectUrl(urlBuffer);
19635
+ const detectedUrl = detectDevServerUrl(urlBuffer);
19541
19636
  if (detectedUrl) {
19542
19637
  urlDetected = true;
19543
19638
  this.detectedUrls.set(projectId, detectedUrl);
@@ -19551,7 +19646,7 @@ var ProcessManager = class extends EventEmitter {
19551
19646
  const text = data.toString();
19552
19647
  urlBuffer += text;
19553
19648
  if (!urlDetected) {
19554
- const detectedUrl = this.detectUrl(urlBuffer);
19649
+ const detectedUrl = detectDevServerUrl(urlBuffer);
19555
19650
  if (detectedUrl) {
19556
19651
  urlDetected = true;
19557
19652
  this.detectedUrls.set(projectId, detectedUrl);
@@ -19642,23 +19737,6 @@ var ProcessManager = class extends EventEmitter {
19642
19737
  getDetectedUrl(projectId) {
19643
19738
  return this.detectedUrls.get(projectId);
19644
19739
  }
19645
- /**
19646
- * Detect URL from text output
19647
- * @param text - The text to search for URLs
19648
- * @returns The first detected URL or undefined
19649
- */
19650
- detectUrl(text) {
19651
- for (const pattern of URL_PATTERNS) {
19652
- const match = text.match(pattern);
19653
- if (match) {
19654
- if (match[1] && !match[0].includes("://")) {
19655
- return `http://localhost:${match[1]}`;
19656
- }
19657
- return match[0];
19658
- }
19659
- }
19660
- return void 0;
19661
- }
19662
19740
  };
19663
19741
 
19664
19742
  // src/features/projects/projects.creator.ts
@@ -20829,10 +20907,10 @@ async function loadUtils() {
20829
20907
  }
20830
20908
  }
20831
20909
  var ProjectCreator = class {
20832
- constructor(rootFolder, metadataManager, gitManager, processingStateManager, runCommandCallbacks) {
20910
+ constructor(rootFolder, metadataManager, gitManager2, processingStateManager, runCommandCallbacks) {
20833
20911
  this.rootFolder = rootFolder;
20834
20912
  this.metadataManager = metadataManager;
20835
- this.gitManager = gitManager;
20913
+ this.gitManager = gitManager2;
20836
20914
  this.processingStateManager = processingStateManager;
20837
20915
  this.runCommandCallbacks = runCommandCallbacks;
20838
20916
  }
@@ -21526,7 +21604,7 @@ var ProjectManagerImpl = class {
21526
21604
  * - 1.1 - WHEN a user provides a git URL to create a new Project, THE CLI SHALL clone the repository into a folder under the Root_Folder
21527
21605
  * - 1.4 - THE CLI SHALL determine the storage path for each Project folder
21528
21606
  */
21529
- constructor(rootFolder, metadataManager, gitManager, processingStateManager) {
21607
+ constructor(rootFolder, metadataManager, gitManager2, processingStateManager) {
21530
21608
  this.rootFolder = rootFolder;
21531
21609
  this.metadataManager = metadataManager;
21532
21610
  this.processManager = new ProcessManager();
@@ -21535,7 +21613,7 @@ var ProjectManagerImpl = class {
21535
21613
  this.projectCreator = new ProjectCreator(
21536
21614
  rootFolder,
21537
21615
  metadataManager,
21538
- gitManager,
21616
+ gitManager2,
21539
21617
  processingStateManager,
21540
21618
  {
21541
21619
  suggestRunCommand: (id) => this.suggestRunCommand(id),
@@ -23326,9 +23404,9 @@ var ThreadManagerImpl = class {
23326
23404
  * - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
23327
23405
  * - 2.4 - THE System SHALL maintain a title for each Thread
23328
23406
  */
23329
- constructor(metadataManager, gitManager, processingStateManager, rootFolder = "") {
23407
+ constructor(metadataManager, gitManager2, processingStateManager, rootFolder = "") {
23330
23408
  this.metadataManager = metadataManager;
23331
- this.gitManager = gitManager;
23409
+ this.gitManager = gitManager2;
23332
23410
  this.processingStateManager = processingStateManager;
23333
23411
  this.rootFolder = rootFolder;
23334
23412
  }
@@ -23807,7 +23885,7 @@ function validateThreadName(name) {
23807
23885
  }
23808
23886
 
23809
23887
  // src/features/threads/threads-create.route.ts
23810
- async function handleCreateThread(c, threadManager, gitManager) {
23888
+ async function handleCreateThread(c, threadManager, gitManager2) {
23811
23889
  try {
23812
23890
  const body = await c.req.json();
23813
23891
  const { projectId, title } = body;
@@ -23833,11 +23911,11 @@ async function handleCreateThread(c, threadManager, gitManager) {
23833
23911
  return errorResponse(c, ErrorCodes.INVALID_REQUEST, nameError, 400);
23834
23912
  }
23835
23913
  const existingThreads = await threadManager.listThreads(projectId);
23836
- const sanitizedNewName = gitManager.sanitizeBranchName(title);
23837
- const existingSanitized = existingThreads.map((t) => gitManager.sanitizeBranchName(t.title));
23914
+ const sanitizedNewName = gitManager2.sanitizeBranchName(title);
23915
+ const existingSanitized = existingThreads.map((t) => gitManager2.sanitizeBranchName(t.title));
23838
23916
  if (existingSanitized.includes(sanitizedNewName)) {
23839
23917
  let fallbackTitle = generateRandomThreadName();
23840
- while (existingSanitized.includes(gitManager.sanitizeBranchName(fallbackTitle))) {
23918
+ while (existingSanitized.includes(gitManager2.sanitizeBranchName(fallbackTitle))) {
23841
23919
  fallbackTitle = generateRandomThreadName();
23842
23920
  }
23843
23921
  return streamAsyncGenerator(c, threadManager.createThread(projectId, fallbackTitle));
@@ -24441,9 +24519,11 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
24441
24519
  if (!thread) {
24442
24520
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
24443
24521
  }
24444
- const history = thread.currentConversationId ? await conversationManager.getConversationHistoryByConversationId(
24522
+ const requestedConversationId = c.req.query("conversationId");
24523
+ const targetConversationId = typeof requestedConversationId === "string" && requestedConversationId.length > 0 ? requestedConversationId : thread.currentConversationId;
24524
+ const history = targetConversationId ? await conversationManager.getConversationHistoryByConversationId(
24445
24525
  threadId,
24446
- thread.currentConversationId
24526
+ targetConversationId
24447
24527
  ) : await conversationManager.getConversationHistory(threadId);
24448
24528
  if (!history || history.messages.length === 0) {
24449
24529
  return c.json([]);
@@ -25568,17 +25648,22 @@ init_utils();
25568
25648
  import { spawnSync as spawnSync2 } from "child_process";
25569
25649
  var ScriptProcessManager = class {
25570
25650
  processes = /* @__PURE__ */ new Map();
25651
+ threadIds = /* @__PURE__ */ new Map();
25571
25652
  /**
25572
25653
  * Run a script and stream its output
25573
25654
  * @param key - Unique key for this script (e.g. "projectId:scriptName")
25574
25655
  * @param command - The shell command to run
25575
25656
  * @param cwd - Working directory for the command
25657
+ * @param threadId - Optional thread ID for dev server URL caching
25576
25658
  * @yields Output chunks from stdout/stderr
25577
25659
  */
25578
- async *runScript(key, command, cwd) {
25660
+ async *runScript(key, command, cwd, threadId) {
25579
25661
  if (this.processes.has(key)) {
25580
25662
  this.stopScript(key);
25581
25663
  }
25664
+ if (threadId) {
25665
+ this.threadIds.set(key, threadId);
25666
+ }
25582
25667
  const { shell, args: shellArgs } = getShellConfig();
25583
25668
  const child = spawnProcess(shell, [...shellArgs, command], {
25584
25669
  cwd,
@@ -25594,16 +25679,39 @@ var ScriptProcessManager = class {
25594
25679
  const decoder = new TextDecoder();
25595
25680
  const outputBuffer = [];
25596
25681
  let isDone = false;
25682
+ let urlDetected = false;
25683
+ let urlBuffer = "";
25684
+ function appendOutput(text, isError) {
25685
+ urlBuffer += text;
25686
+ if (!urlDetected) {
25687
+ const detectedUrl = detectDevServerUrl(urlBuffer);
25688
+ if (detectedUrl) {
25689
+ urlDetected = true;
25690
+ if (threadId) {
25691
+ devServerCache.setUrl(threadId, detectedUrl);
25692
+ }
25693
+ outputBuffer.push({ type: "url", url: detectedUrl });
25694
+ }
25695
+ }
25696
+ if (isError) {
25697
+ outputBuffer.push({ type: "error", message: text });
25698
+ } else {
25699
+ outputBuffer.push(text);
25700
+ }
25701
+ }
25597
25702
  child.stdout?.on("data", (data) => {
25598
- outputBuffer.push(decoder.decode(data));
25703
+ appendOutput(decoder.decode(data), false);
25599
25704
  });
25600
25705
  child.stderr?.on("data", (data) => {
25601
- outputBuffer.push({ type: "error", message: decoder.decode(data) });
25706
+ appendOutput(decoder.decode(data), true);
25602
25707
  });
25603
25708
  child.on("close", (code) => {
25604
25709
  if (code !== 0 && code !== null) {
25605
25710
  outputBuffer.push({ type: "error", message: `Process exited with code ${code}` });
25606
25711
  }
25712
+ if (threadId && urlDetected) {
25713
+ devServerCache.clearUrl(threadId);
25714
+ }
25607
25715
  isDone = true;
25608
25716
  });
25609
25717
  child.on("error", (err) => {
@@ -25620,6 +25728,7 @@ var ScriptProcessManager = class {
25620
25728
  }
25621
25729
  } finally {
25622
25730
  this.processes.delete(key);
25731
+ this.threadIds.delete(key);
25623
25732
  }
25624
25733
  }
25625
25734
  /**
@@ -25630,8 +25739,13 @@ var ScriptProcessManager = class {
25630
25739
  stopScript(key) {
25631
25740
  const entry = this.processes.get(key);
25632
25741
  if (!entry) return false;
25742
+ const threadId = this.threadIds.get(key);
25633
25743
  killProcessTree2(entry.pid, entry.process);
25634
25744
  this.processes.delete(key);
25745
+ this.threadIds.delete(key);
25746
+ if (threadId) {
25747
+ devServerCache.clearUrl(threadId);
25748
+ }
25635
25749
  return true;
25636
25750
  }
25637
25751
  /**
@@ -25706,7 +25820,10 @@ async function handleRunProjectScript(c, threadManager) {
25706
25820
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
25707
25821
  }
25708
25822
  const key = `${thread.projectId}:${scriptName}`;
25709
- return streamAsyncGenerator(c, scriptProcessManager.runScript(key, command, thread.path));
25823
+ return streamAsyncGenerator(
25824
+ c,
25825
+ scriptProcessManager.runScript(key, command, thread.path, threadId)
25826
+ );
25710
25827
  } catch (error) {
25711
25828
  return errorResponse(
25712
25829
  c,
@@ -25748,10 +25865,10 @@ async function handleStopProjectScript(c, threadManager) {
25748
25865
  }
25749
25866
 
25750
25867
  // src/features/threads/threads.routes.ts
25751
- function createThreadRoutes(threadManager, gitManager, conversationManager) {
25868
+ function createThreadRoutes(threadManager, gitManager2, conversationManager) {
25752
25869
  const router = new Hono12();
25753
25870
  router.post("/", async (c) => {
25754
- return handleCreateThread(c, threadManager, gitManager);
25871
+ return handleCreateThread(c, threadManager, gitManager2);
25755
25872
  });
25756
25873
  router.get("/", async (c) => {
25757
25874
  const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), database_exports));
@@ -26476,9 +26593,63 @@ async function gitGenerateCommitMessageHandler(c, metadataManager) {
26476
26593
  }
26477
26594
 
26478
26595
  // src/features/git/git-commit.route.ts
26596
+ var gitManager = new GitManagerImpl();
26597
+ async function getUniqueBranchName(baseBranchName, gitRoot, existingBranchNames) {
26598
+ async function isBranchNameTaken(branchName) {
26599
+ if (existingBranchNames.has(branchName)) {
26600
+ return true;
26601
+ }
26602
+ return checkBranchExists(gitRoot, branchName);
26603
+ }
26604
+ if (!await isBranchNameTaken(baseBranchName)) {
26605
+ return baseBranchName;
26606
+ }
26607
+ let counter = 2;
26608
+ while (await isBranchNameTaken(`${baseBranchName}-${counter}`)) {
26609
+ counter++;
26610
+ }
26611
+ return `${baseBranchName}-${counter}`;
26612
+ }
26613
+ async function maybeRenameInitialTarskBranch(gitRoot, threadId, commitMessage, metadataManager) {
26614
+ const [commitCountBeforeCommit, currentBranch, threads] = await Promise.all([
26615
+ getCommitCount(gitRoot),
26616
+ getCurrentBranch(gitRoot),
26617
+ metadataManager.loadThreads()
26618
+ ]);
26619
+ if (commitCountBeforeCommit !== 0) {
26620
+ return;
26621
+ }
26622
+ const thread = threads.find((candidate) => candidate.id === threadId);
26623
+ if (!thread) {
26624
+ return;
26625
+ }
26626
+ if (currentBranch !== thread.currentBranch) {
26627
+ return;
26628
+ }
26629
+ const expectedTarskBranchName = gitManager.sanitizeBranchName(thread.title);
26630
+ if (currentBranch !== expectedTarskBranchName) {
26631
+ return;
26632
+ }
26633
+ const requestedBranchName = gitManager.sanitizeBranchName(commitMessage);
26634
+ if (!requestedBranchName || requestedBranchName === currentBranch) {
26635
+ return;
26636
+ }
26637
+ const existingBranchNames = new Set(threads.map((candidate) => candidate.currentBranch));
26638
+ existingBranchNames.delete(currentBranch);
26639
+ const newBranchName = await getUniqueBranchName(
26640
+ requestedBranchName,
26641
+ gitRoot,
26642
+ existingBranchNames
26643
+ );
26644
+ await renameCurrentBranch(gitRoot, newBranchName);
26645
+ await metadataManager.updateThreadFields(threadId, { currentBranch: newBranchName });
26646
+ }
26479
26647
  async function gitCommitHandler(c, metadataManager) {
26480
26648
  try {
26481
26649
  const threadId = c.req.param("threadId");
26650
+ if (!threadId) {
26651
+ return c.json({ error: "Thread ID is required" }, 400);
26652
+ }
26482
26653
  const body = await c.req.json();
26483
26654
  const { message } = body;
26484
26655
  if (!message) {
@@ -26500,6 +26671,7 @@ async function gitCommitHandler(c, metadataManager) {
26500
26671
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
26501
26672
  }
26502
26673
  await stageAllChanges(gitRoot);
26674
+ await maybeRenameInitialTarskBranch(gitRoot, threadId, message, metadataManager);
26503
26675
  await commitChanges(gitRoot, message);
26504
26676
  return c.json({ success: true });
26505
26677
  } catch (error) {
@@ -26803,6 +26975,9 @@ async function gitLogHandler(c, metadataManager) {
26803
26975
  }
26804
26976
 
26805
26977
  // src/features/git/git-create-pr.route.ts
26978
+ function normalizeThreadTitle(title) {
26979
+ return (title ?? "").trim().replace(/\s+/g, " ");
26980
+ }
26806
26981
  async function gitCreatePrHandler(c, metadataManager) {
26807
26982
  try {
26808
26983
  const threadId = c.req.param("threadId");
@@ -26812,6 +26987,9 @@ async function gitCreatePrHandler(c, metadataManager) {
26812
26987
  console.log(`[git-create-pr] PR title: "${title}"`);
26813
26988
  console.log(`[git-create-pr] PR description length: ${description?.length ?? 0} chars`);
26814
26989
  const thread = await metadataManager.loadThreads().then((threads) => threads.find((t) => t.id === threadId));
26990
+ if (!threadId) {
26991
+ return c.json({ error: "Thread ID is required" }, 400);
26992
+ }
26815
26993
  if (!thread) {
26816
26994
  console.log(`[git-create-pr] Thread not found: ${threadId}`);
26817
26995
  return c.json({ error: "Thread not found" }, 404);
@@ -26834,10 +27012,17 @@ async function gitCreatePrHandler(c, metadataManager) {
26834
27012
  }
26835
27013
  const currentBranch = await getCurrentBranch(gitRoot);
26836
27014
  console.log(`[git-create-pr] Current branch: ${currentBranch}`);
27015
+ const prTitle = title ?? currentBranch;
26837
27016
  console.log(`[git-create-pr] Creating PR with GitHub CLI...`);
26838
- const prUrl = await createPullRequest(gitRoot, title ?? currentBranch, description ?? "");
27017
+ const prUrl = await createPullRequest(gitRoot, prTitle, description ?? "");
26839
27018
  console.log(`[git-create-pr] \u2713 PR created successfully: ${prUrl}`);
26840
- return c.json({ success: true, prUrl });
27019
+ const normalizedPrTitle = normalizeThreadTitle(prTitle);
27020
+ if (normalizedPrTitle !== thread.title) {
27021
+ const threadUpdates = { title: normalizedPrTitle };
27022
+ await metadataManager.updateThreadFields(threadId, threadUpdates);
27023
+ console.log(`[git-create-pr] \u2713 Updated thread title to PR title: ${normalizedPrTitle}`);
27024
+ }
27025
+ return c.json({ success: true, prUrl, title: normalizedPrTitle });
26841
27026
  } catch (error) {
26842
27027
  const message = error instanceof Error ? error.message : "Failed to create PR";
26843
27028
  console.log(`[git-create-pr] \u2717 PR creation failed: ${message}`);
@@ -27163,7 +27348,7 @@ function sanitizeBranchName(name) {
27163
27348
  }
27164
27349
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
27165
27350
  }
27166
- function checkBranchExists(gitRoot, branchName) {
27351
+ function checkBranchExists2(gitRoot, branchName) {
27167
27352
  return new Promise((resolve8) => {
27168
27353
  const proc = spawnProcess(
27169
27354
  "git",
@@ -27227,12 +27412,12 @@ async function gitCreateRepoHandler(c, metadataManager) {
27227
27412
  }
27228
27413
  const repoUrl = await createGitHubRepo(gitRoot, repoName, description, isPrivate);
27229
27414
  let branchName = sanitizeBranchName(generateRandomThreadName());
27230
- let branchExists = await checkBranchExists(gitRoot, branchName);
27415
+ let branchExists = await checkBranchExists2(gitRoot, branchName);
27231
27416
  let counter = 2;
27232
27417
  const baseBranchName = branchName;
27233
27418
  while (branchExists) {
27234
27419
  branchName = `${baseBranchName}-${counter}`;
27235
- branchExists = await checkBranchExists(gitRoot, branchName);
27420
+ branchExists = await checkBranchExists2(gitRoot, branchName);
27236
27421
  counter++;
27237
27422
  }
27238
27423
  await createAndCheckoutBranch(gitRoot, branchName);
@@ -27285,7 +27470,7 @@ function sanitizeBranchName2(name) {
27285
27470
  }
27286
27471
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
27287
27472
  }
27288
- function checkBranchExists2(gitRoot, branchName) {
27473
+ function checkBranchExists3(gitRoot, branchName) {
27289
27474
  return new Promise((resolve8) => {
27290
27475
  const proc = spawnProcess(
27291
27476
  "git",
@@ -27348,7 +27533,7 @@ async function gitCreateBranchHandler(c, metadataManager) {
27348
27533
  if (!branchName) {
27349
27534
  return c.json({ error: "Invalid branch name" }, 400);
27350
27535
  }
27351
- const branchExists = await checkBranchExists2(gitRoot, branchName);
27536
+ const branchExists = await checkBranchExists3(gitRoot, branchName);
27352
27537
  if (body.branchName) {
27353
27538
  if (branchExists) {
27354
27539
  return c.json({ error: `Branch "${branchName}" already exists` }, 409);
@@ -27359,7 +27544,7 @@ async function gitCreateBranchHandler(c, metadataManager) {
27359
27544
  let exists = branchExists;
27360
27545
  while (exists) {
27361
27546
  branchName = `${baseBranchName}-${counter}`;
27362
- exists = await checkBranchExists2(gitRoot, branchName);
27547
+ exists = await checkBranchExists3(gitRoot, branchName);
27363
27548
  counter++;
27364
27549
  }
27365
27550
  }
@@ -27974,51 +28159,12 @@ async function gitCheckpointRestoreHandler(c, metadataManager) {
27974
28159
  }
27975
28160
 
27976
28161
  // src/features/git/git-rename-branch.route.ts
27977
- init_utils();
27978
28162
  function sanitizeBranchName3(name) {
27979
28163
  if (!name || typeof name !== "string") {
27980
28164
  return "";
27981
28165
  }
27982
28166
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "";
27983
28167
  }
27984
- function checkBranchExists3(gitRoot, branchName) {
27985
- return new Promise((resolve8) => {
27986
- const proc = spawnProcess(
27987
- "git",
27988
- ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
27989
- {
27990
- cwd: gitRoot
27991
- }
27992
- );
27993
- proc.on("close", (code) => {
27994
- resolve8(code === 0);
27995
- });
27996
- proc.on("error", () => {
27997
- resolve8(false);
27998
- });
27999
- });
28000
- }
28001
- function renameBranch(gitRoot, newBranchName) {
28002
- return new Promise((resolve8, reject) => {
28003
- const proc = spawnProcess("git", ["branch", "-m", newBranchName], {
28004
- cwd: gitRoot
28005
- });
28006
- let err = "";
28007
- if (proc.stderr) {
28008
- proc.stderr.on("data", (d) => {
28009
- err += d.toString();
28010
- });
28011
- }
28012
- proc.on("close", (code) => {
28013
- if (code === 0) {
28014
- resolve8();
28015
- } else {
28016
- reject(new Error(err || `Failed to rename branch to "${newBranchName}"`));
28017
- }
28018
- });
28019
- proc.on("error", reject);
28020
- });
28021
- }
28022
28168
  async function gitRenameBranchHandler(c, metadataManager) {
28023
28169
  try {
28024
28170
  const threadId = c.req.param("threadId");
@@ -28046,11 +28192,11 @@ async function gitRenameBranchHandler(c, metadataManager) {
28046
28192
  } catch {
28047
28193
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
28048
28194
  }
28049
- const branchExists = await checkBranchExists3(gitRoot, newBranchName);
28195
+ const branchExists = await checkBranchExists(gitRoot, newBranchName);
28050
28196
  if (branchExists) {
28051
28197
  return c.json({ error: `Branch "${newBranchName}" already exists` }, 409);
28052
28198
  }
28053
- await renameBranch(gitRoot, newBranchName);
28199
+ await renameCurrentBranch(gitRoot, newBranchName);
28054
28200
  const threads = await metadataManager.loadThreads();
28055
28201
  const threadIndex = threads.findIndex((t) => t.id === threadId);
28056
28202
  if (threadIndex !== -1) {
@@ -29431,23 +29577,12 @@ function createBrowserRoutes() {
29431
29577
 
29432
29578
  // src/features/voice-model/voice-model.routes.ts
29433
29579
  import { Hono as Hono24 } from "hono";
29434
- var VOICE_MODEL_URLS = {
29435
- default: "https://install.tarsk.io/voice-models/ggml-tiny.en.bin",
29436
- tiny: "https://install.tarsk.io/voice-models/ggml-tiny.en-q5_1.bin"
29437
- };
29438
- function getVoiceModelUrl(model) {
29439
- if (model === "tiny") {
29440
- return VOICE_MODEL_URLS.tiny;
29441
- }
29442
- return VOICE_MODEL_URLS.default;
29443
- }
29580
+ var VOICE_MODEL_URL = "https://install.tarsk.io/voice-models/vosk-model-small-en-us-0.15.tar.gz";
29444
29581
  function createVoiceModelRoutes() {
29445
29582
  const router = new Hono24();
29446
29583
  router.get("/download", async (c) => {
29447
- const selectedModel = c.req.query("model");
29448
- const modelUrl = getVoiceModelUrl(selectedModel);
29449
29584
  try {
29450
- const response = await fetch(modelUrl);
29585
+ const response = await fetch(VOICE_MODEL_URL);
29451
29586
  if (!response.ok) {
29452
29587
  return errorResponse(
29453
29588
  c,
@@ -29458,8 +29593,7 @@ function createVoiceModelRoutes() {
29458
29593
  }
29459
29594
  const contentLength = response.headers.get("content-length");
29460
29595
  const headers = {
29461
- "Content-Type": "application/octet-stream",
29462
- "X-Tarsk-Voice-Model": selectedModel === "tiny" ? "tiny" : "default"
29596
+ "Content-Type": "application/gzip"
29463
29597
  };
29464
29598
  if (contentLength) {
29465
29599
  headers["Content-Length"] = contentLength;
@@ -29675,7 +29809,7 @@ async function startTarskServer(options) {
29675
29809
  }
29676
29810
  async function startTarskServerInternal(options) {
29677
29811
  const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
29678
- const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
29812
+ const initialPort = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
29679
29813
  const app = new Hono28();
29680
29814
  app.use("/*", cors());
29681
29815
  app.use("/*", async (c, next) => {
@@ -29687,7 +29821,9 @@ async function startTarskServerInternal(options) {
29687
29821
  if (c.req.path.startsWith("/api/")) {
29688
29822
  const method = c.req.method;
29689
29823
  const reqPath = c.req.path;
29690
- const fullUrl = `http://localhost:${port}${reqPath}`;
29824
+ const hostHeader = c.req.header("host") ?? `localhost:${initialPort}`;
29825
+ const requestUrl = new URL(c.req.url);
29826
+ const fullUrl = `${requestUrl.protocol}//${hostHeader}${reqPath}`;
29691
29827
  const isThreadMessages = reqPath.startsWith("/api/threads/") && reqPath.endsWith("/messages");
29692
29828
  if (!fullUrl.includes("processing") && !reqPath.startsWith("/api/logs") && !isThreadMessages) {
29693
29829
  console.log(`${method} ${fullUrl}`);
@@ -29697,17 +29833,17 @@ async function startTarskServerInternal(options) {
29697
29833
  });
29698
29834
  const dataDir = getDataDir();
29699
29835
  const metadataManager = new MetadataManager(dataDir);
29700
- const gitManager = new GitManagerImpl();
29836
+ const gitManager2 = new GitManagerImpl();
29701
29837
  const processingStateManager = new ProcessingStateManagerImpl();
29702
29838
  const projectManager = new ProjectManagerImpl(
29703
29839
  dataDir,
29704
29840
  metadataManager,
29705
- gitManager,
29841
+ gitManager2,
29706
29842
  processingStateManager
29707
29843
  );
29708
29844
  const threadManager = new ThreadManagerImpl(
29709
29845
  metadataManager,
29710
- gitManager,
29846
+ gitManager2,
29711
29847
  processingStateManager,
29712
29848
  dataDir
29713
29849
  );
@@ -29720,7 +29856,8 @@ async function startTarskServerInternal(options) {
29720
29856
  return c.json({
29721
29857
  status: "ok",
29722
29858
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
29723
- service: "project-threads-manager-cli"
29859
+ service: "project-threads-manager-cli",
29860
+ isDevMode: port !== initialPort
29724
29861
  });
29725
29862
  });
29726
29863
  app.get("/api/programs", (c) => {
@@ -29736,7 +29873,7 @@ async function startTarskServerInternal(options) {
29736
29873
  app.route("/api/projects", createProjectRoutes(projectManager, threadManager));
29737
29874
  app.route("/api/projects", createRunRoutes(projectManager));
29738
29875
  app.route("/api/projects", createProjectTodosRoutes(metadataManager));
29739
- app.route("/api/threads", createThreadRoutes(threadManager, gitManager, conversationManager));
29876
+ app.route("/api/threads", createThreadRoutes(threadManager, gitManager2, conversationManager));
29740
29877
  app.route("/api/threads", createUserTaskRoutes());
29741
29878
  app.route(
29742
29879
  "/api/chat",
@@ -29786,16 +29923,37 @@ async function startTarskServerInternal(options) {
29786
29923
  `No static frontend assets found. Expected one of: ${prodPublicDir}, ${devCopiedPublicDir}, ${appDistDir}. Build the app first with \`cd ../app && bun run build\`.`
29787
29924
  );
29788
29925
  }
29926
+ function isStaticAssetPath(requestPath) {
29927
+ if (requestPath.startsWith("/assets/")) {
29928
+ return true;
29929
+ }
29930
+ const lastSegment = requestPath.split("/").pop() ?? "";
29931
+ return lastSegment.includes(".") && !lastSegment.endsWith(".html");
29932
+ }
29933
+ function setNoCacheIndexHeaders(c) {
29934
+ c.header("Cache-Control", "no-cache, no-store, must-revalidate");
29935
+ }
29789
29936
  app.use("/*", async (c, next) => {
29790
29937
  if (c.req.path.startsWith("/api/")) {
29791
29938
  return next();
29792
29939
  }
29793
- return serveStatic({ root: resolvedPublicDir })(c, next);
29940
+ return serveStatic({
29941
+ root: resolvedPublicDir,
29942
+ onFound: (_path, context) => {
29943
+ if (context.req.path === "/" || context.req.path === "/index.html") {
29944
+ setNoCacheIndexHeaders(context);
29945
+ }
29946
+ }
29947
+ })(c, next);
29794
29948
  });
29795
29949
  app.get("*", async (c, next) => {
29796
29950
  if (c.req.path.startsWith("/api/")) {
29797
29951
  return next();
29798
29952
  }
29953
+ if (isStaticAssetPath(c.req.path)) {
29954
+ return next();
29955
+ }
29956
+ setNoCacheIndexHeaders(c);
29799
29957
  return serveStatic({
29800
29958
  path: path5.join(resolvedPublicDir, "index.html")
29801
29959
  })(c, next);
@@ -29812,16 +29970,22 @@ async function startTarskServerInternal(options) {
29812
29970
  });
29813
29971
  const serverBindMode = readServerBindMode();
29814
29972
  const hostname2 = getServerBindHostname(serverBindMode);
29815
- const url = `http://localhost:${port}`;
29816
- const bound = await listenForTarskServer({
29973
+ const port = await listenForTarskServer({
29817
29974
  fetch: app.fetch,
29818
29975
  hostname: hostname2,
29819
- onListening: () => {
29976
+ onPortConflict: (candidatePort) => {
29977
+ process.stdout.write(
29978
+ `Port ${candidatePort} is already in use, trying ${candidatePort + 1}.
29979
+ `
29980
+ );
29981
+ },
29982
+ onListening: (listeningPort) => {
29983
+ const url = `http://localhost:${listeningPort}`;
29820
29984
  process.stdout.write(`Tarsk started on ${url}
29821
29985
  `);
29822
29986
  if (serverBindMode === "network") {
29823
29987
  for (const address of getLocalNetworkAddresses()) {
29824
- process.stdout.write(` Network: http://${address}:${port}
29988
+ process.stdout.write(` Network: http://${address}:${listeningPort}
29825
29989
  `);
29826
29990
  }
29827
29991
  }
@@ -29830,38 +29994,48 @@ async function startTarskServerInternal(options) {
29830
29994
  });
29831
29995
  }
29832
29996
  },
29833
- port
29997
+ port: initialPort
29834
29998
  });
29835
- if (!bound) {
29836
- return { url, port, bound: false };
29837
- }
29838
- return { url, port, bound: true };
29999
+ return {
30000
+ url: `http://localhost:${port}`,
30001
+ port,
30002
+ bound: true,
30003
+ isDevMode: port !== initialPort
30004
+ };
29839
30005
  }
29840
30006
  async function listenForTarskServer(options) {
29841
- const server = createAdaptorServer({
29842
- fetch: options.fetch,
29843
- hostname: options.hostname,
29844
- port: options.port
29845
- });
29846
- return new Promise((resolve8, reject) => {
29847
- server.once("error", (error) => {
29848
- if (error.code === "EADDRINUSE") {
29849
- resolve8(false);
29850
- return;
30007
+ let port = options.port;
30008
+ while (true) {
30009
+ const server = createAdaptorServer({
30010
+ fetch: options.fetch,
30011
+ hostname: options.hostname,
30012
+ port
30013
+ });
30014
+ const result = await new Promise((resolve8, reject) => {
30015
+ server.once("error", (error) => {
30016
+ if (error.code === "EADDRINUSE") {
30017
+ resolve8("in-use");
30018
+ return;
30019
+ }
30020
+ startPromise = null;
30021
+ reject(error);
30022
+ });
30023
+ function onListening() {
30024
+ resolve8("listening");
30025
+ }
30026
+ if (options.hostname) {
30027
+ server.listen(port, options.hostname, onListening);
30028
+ } else {
30029
+ server.listen(port, onListening);
29851
30030
  }
29852
- startPromise = null;
29853
- reject(error);
29854
30031
  });
29855
- function onListening() {
29856
- options.onListening();
29857
- resolve8(true);
29858
- }
29859
- if (options.hostname) {
29860
- server.listen(options.port, options.hostname, onListening);
29861
- } else {
29862
- server.listen(options.port, onListening);
30032
+ if (result === "listening") {
30033
+ options.onListening(port);
30034
+ return port;
29863
30035
  }
29864
- });
30036
+ options.onPortConflict(port);
30037
+ port += 1;
30038
+ }
29865
30039
  }
29866
30040
 
29867
30041
  // src/index.ts