@sideboard-ai/core 0.1.109 → 0.1.110

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 (43) hide show
  1. package/dist/agents/cursor-runner.cjs +87 -12
  2. package/dist/agents/cursor-runner.js +37 -11
  3. package/dist/{agents-XB332VUJ.js → agents-MQDW3YXL.js} +6 -6
  4. package/dist/{agents-YIMHDI46.js → agents-WK54VP7J.js} +7 -7
  5. package/dist/{app-settings-EBCVMFF6.js → app-settings-5XAGNDX7.js} +3 -1
  6. package/dist/{app-settings-IYSSVZHH.js → app-settings-J4LUWIRN.js} +3 -1
  7. package/dist/{chunk-WD2TARQS.js → chunk-3UOKB6VQ.js} +40 -0
  8. package/dist/{chunk-756EO4GK.js → chunk-4EWPKYOU.js} +4 -4
  9. package/dist/{chunk-4LKD3PGO.js → chunk-4NR5FL46.js} +10 -0
  10. package/dist/{chunk-FBV6NK62.js → chunk-7KWYXGIU.js} +2 -2
  11. package/dist/{chunk-KGSVLZV6.js → chunk-A4VZXJEJ.js} +2 -0
  12. package/dist/{chunk-VRTKGKUW.js → chunk-AROMOP3C.js} +2 -2
  13. package/dist/{chunk-EW7COXYE.js → chunk-C4KHDW3U.js} +2 -2
  14. package/dist/{chunk-Q6B3NRCT.js → chunk-EAAB4EK5.js} +154 -287
  15. package/dist/{chunk-I2OJ2YSP.js → chunk-KQBI5HNT.js} +2 -2
  16. package/dist/{chunk-P3BQ5DOL.js → chunk-KYOTBZ6S.js} +2 -0
  17. package/dist/{chunk-KKHDPV45.js → chunk-RSIWPLRG.js} +473 -37
  18. package/dist/{chunk-NE4TOOKS.js → chunk-SGEVS5SY.js} +10 -0
  19. package/dist/{chunk-TROJ3PVH.js → chunk-VOD3HFLP.js} +4 -4
  20. package/dist/{chunk-AY2EQI2P.js → chunk-VOJSO3RM.js} +472 -46
  21. package/dist/{chunk-MMI4RJ5I.js → chunk-WIHKHR5R.js} +14 -3
  22. package/dist/{chunk-LTPX5N6K.js → chunk-XOZDAVOP.js} +162 -276
  23. package/dist/{chunk-OQDIYVDD.js → chunk-YFAXVUY2.js} +2 -2
  24. package/dist/{chunk-JVIW55KT.js → chunk-YXDR43ZC.js} +2 -2
  25. package/dist/{coordinator-prompt-JKOU6BWV.js → coordinator-prompt-AR66L3N4.js} +4 -4
  26. package/dist/{coordinator-prompt-V66BYTUV.js → coordinator-prompt-BHMLWL64.js} +4 -4
  27. package/dist/{global-workspace-WBQWQQQY.js → global-workspace-SKFODUQQ.js} +5 -5
  28. package/dist/{global-workspace-NCTBE7G7.js → global-workspace-XSUFEIAQ.js} +5 -5
  29. package/dist/index.cjs +835 -489
  30. package/dist/index.d.cts +51 -3
  31. package/dist/index.d.ts +51 -3
  32. package/dist/index.js +44 -23
  33. package/dist/mcp/run-stdio.cjs +812 -487
  34. package/dist/mcp/run-stdio.js +17 -12
  35. package/dist/{orchestrator-6W7EKSPO.js → orchestrator-FCZVCKBK.js} +10 -10
  36. package/dist/{orchestrator-JVGVPKLI.js → orchestrator-SCATSB3O.js} +8 -8
  37. package/dist/{thread-store-DQJGDKIJ.js → thread-store-LPTBVW5C.js} +1 -1
  38. package/dist/{thread-store-4OUEQ2DB.js → thread-store-OEALWU7Y.js} +1 -1
  39. package/dist/{workspaces-SUBEDSGB.js → workspaces-HV3J4TTW.js} +6 -6
  40. package/dist/{workspaces-I3554R3F.js → workspaces-XAQVKTLO.js} +6 -6
  41. package/dist/{worktree-7H6HB6GW.js → worktree-N2EV24EE.js} +3 -3
  42. package/dist/{worktree-2UWO7VEK.js → worktree-XFJED3VU.js} +3 -3
  43. package/package.json +1 -1
@@ -702,6 +702,7 @@ function normalizeThread(raw) {
702
702
  prTitle: raw.prTitle ?? null,
703
703
  prState: raw.prState ?? null,
704
704
  skipAutoArchiveOnMerge: Boolean(raw.skipAutoArchiveOnMerge),
705
+ cowboy: Boolean(raw.cowboy),
705
706
  stackId: raw.stackId ?? null,
706
707
  stackLayer: raw.stackLayer ?? null,
707
708
  userSetTitle: Boolean(raw.userSetTitle),
@@ -730,6 +731,7 @@ function createEmptyThread(partial) {
730
731
  prTitle: partial.prTitle ?? null,
731
732
  prState: partial.prState ?? null,
732
733
  skipAutoArchiveOnMerge: partial.skipAutoArchiveOnMerge ?? false,
734
+ cowboy: Boolean(partial.cowboy),
733
735
  stackId: partial.stackId ?? null,
734
736
  stackLayer: partial.stackLayer ?? null,
735
737
  userSetTitle: partial.userSetTitle ?? false,
@@ -2559,6 +2561,7 @@ __export(app_settings_exports, {
2559
2561
  childEnvWithAppSettings: () => childEnvWithAppSettings,
2560
2562
  claudeChromeEnabled: () => claudeChromeEnabled,
2561
2563
  claudeUserSettingsPath: () => claudeUserSettingsPath,
2564
+ cowboyModeEnabled: () => cowboyModeEnabled,
2562
2565
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
2563
2566
  disconnectLinearConnection: () => disconnectLinearConnection,
2564
2567
  ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
@@ -2797,6 +2800,9 @@ function normalizeAdvanced(raw) {
2797
2800
  if (typeof source.deleteBranchOnPurge === "boolean") {
2798
2801
  out.deleteBranchOnPurge = source.deleteBranchOnPurge;
2799
2802
  }
2803
+ if (typeof source.cowboyMode === "boolean") {
2804
+ out.cowboyMode = source.cowboyMode;
2805
+ }
2800
2806
  if (typeof source.autoArchiveOnMerge === "boolean") {
2801
2807
  out.autoArchiveOnMerge = source.autoArchiveOnMerge;
2802
2808
  }
@@ -3321,6 +3327,9 @@ function updateAdvancedSettings(patch) {
3321
3327
  if (typeof patch.deleteBranchOnPurge === "boolean") {
3322
3328
  advanced.deleteBranchOnPurge = patch.deleteBranchOnPurge;
3323
3329
  }
3330
+ if (typeof patch.cowboyMode === "boolean") {
3331
+ advanced.cowboyMode = patch.cowboyMode;
3332
+ }
3324
3333
  if (typeof patch.autoArchiveOnMerge === "boolean") {
3325
3334
  advanced.autoArchiveOnMerge = patch.autoArchiveOnMerge;
3326
3335
  }
@@ -3371,6 +3380,9 @@ function caffeinateWhileCloudConnectEnabled(settings = loadAppSettings()) {
3371
3380
  function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
3372
3381
  return Boolean(settings.advanced.deleteBranchOnPurge);
3373
3382
  }
3383
+ function cowboyModeEnabled(settings = loadAppSettings()) {
3384
+ return Boolean(settings.advanced.cowboyMode);
3385
+ }
3374
3386
  function autoArchiveOnMergeEnabled(settings = loadAppSettings()) {
3375
3387
  return Boolean(settings.advanced.autoArchiveOnMerge);
3376
3388
  }
@@ -6675,6 +6687,296 @@ var init_claude_mcp = __esm({
6675
6687
  }
6676
6688
  });
6677
6689
 
6690
+ // src/agents/message-parts.ts
6691
+ function asRecord(input) {
6692
+ if (input && typeof input === "object" && !Array.isArray(input)) {
6693
+ return input;
6694
+ }
6695
+ return void 0;
6696
+ }
6697
+ function str2(v) {
6698
+ return typeof v === "string" && v.trim() ? v : void 0;
6699
+ }
6700
+ function toolDetail(name, input) {
6701
+ if (!input) return void 0;
6702
+ const command = str2(input.command) ?? str2(input.cmd);
6703
+ if (command) return command;
6704
+ const path = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
6705
+ if (path) return path;
6706
+ const pattern = str2(input.pattern) ?? str2(input.glob) ?? str2(input.glob_pattern);
6707
+ if (pattern) return pattern;
6708
+ const query = str2(input.query) ?? str2(input.prompt);
6709
+ if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
6710
+ try {
6711
+ const raw = JSON.stringify(input);
6712
+ return raw.length > 80 ? `${raw.slice(0, 77)}\u2026` : raw;
6713
+ } catch {
6714
+ return name;
6715
+ }
6716
+ }
6717
+ function toolDescription(name, input) {
6718
+ const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
6719
+ if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
6720
+ if (/connectedAgentRequest/i.test(name)) {
6721
+ return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
6722
+ }
6723
+ if (/present_artifact$/i.test(name)) {
6724
+ return str2(input?.title) ? `Present ${str2(input?.title)}` : "Present artifact";
6725
+ }
6726
+ if (/present_plan$/i.test(name)) {
6727
+ return str2(input?.title) ? `Plan ${str2(input?.title)}` : "Present plan";
6728
+ }
6729
+ if (/present_schema$/i.test(name)) {
6730
+ return str2(input?.title) ? `Schema ${str2(input?.title)}` : "Present schema";
6731
+ }
6732
+ if (/present_files$/i.test(name)) {
6733
+ return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
6734
+ }
6735
+ if (isSubagentToolName(name)) {
6736
+ const desc = str2(input?.description);
6737
+ const sub = asRecord(input?.subagentType);
6738
+ const kind = str2(sub?.name) ?? str2(sub?.kind) ?? str2(input?.subagent_type) ?? str2(input?.subagentType);
6739
+ if (/^spawn_agent$/i.test(name)) {
6740
+ return str2(input?.prompt) ?? desc ?? "Subagent";
6741
+ }
6742
+ if (/connectedAgentRequest/i.test(name)) {
6743
+ return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
6744
+ }
6745
+ if (desc && kind) return `${kind}: ${desc}`;
6746
+ if (desc) return desc;
6747
+ return "Subagent";
6748
+ }
6749
+ if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
6750
+ return str2(input?.title) ? `Artifact ${str2(input?.title)}` : "Artifact";
6751
+ }
6752
+ if (!input) return n;
6753
+ if (/bash|shell|terminal/i.test(name) && str2(input.command)) {
6754
+ const cmd = str2(input.command);
6755
+ if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
6756
+ if (/git\s+status/i.test(cmd)) return "Check git status";
6757
+ if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
6758
+ if (/git\s+branch/i.test(cmd)) return "List branches";
6759
+ if (/grep|rg\b/i.test(cmd)) return "Search repository";
6760
+ return "Run shell command";
6761
+ }
6762
+ if (/edit|write|apply/i.test(name)) {
6763
+ const path = str2(input.file_path) ?? str2(input.path);
6764
+ return path ? `Edit ${path.split("/").pop()}` : "Edit file";
6765
+ }
6766
+ if (/read/i.test(name)) {
6767
+ const path = str2(input.file_path) ?? str2(input.path);
6768
+ return path ? `Read ${path.split("/").pop()}` : "Read file";
6769
+ }
6770
+ if (/grep/i.test(name)) return "Search files";
6771
+ if (/glob/i.test(name)) return "Find files";
6772
+ return n;
6773
+ }
6774
+ function isSubagentToolName(name) {
6775
+ const n = (name ?? "").trim();
6776
+ if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
6777
+ return /connectedAgentRequest/i.test(n);
6778
+ }
6779
+ function messagePartParentId(part) {
6780
+ if ("parentId" in part && typeof part.parentId === "string" && part.parentId.trim()) {
6781
+ return part.parentId;
6782
+ }
6783
+ return void 0;
6784
+ }
6785
+ function sameParentId(a, b) {
6786
+ return (a ?? "") === (b ?? "");
6787
+ }
6788
+ function withEventParentId(event, parentId) {
6789
+ if (!parentId) return event;
6790
+ if (event.type === "stdout" || event.type === "thinking" || event.type === "tool_use" || event.type === "tool_result") {
6791
+ return { ...event, parentId: event.parentId ?? parentId };
6792
+ }
6793
+ return event;
6794
+ }
6795
+ function toolFilePath(input) {
6796
+ if (!input) return void 0;
6797
+ return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
6798
+ }
6799
+ function countLines(text4) {
6800
+ if (!text4) return 0;
6801
+ return text4.split("\n").length;
6802
+ }
6803
+ function diffFromInput(input) {
6804
+ if (!input) return {};
6805
+ const oldS = str2(input.old_string) ?? str2(input.oldString);
6806
+ const newS = str2(input.new_string) ?? str2(input.newString) ?? str2(input.content);
6807
+ if (oldS != null || newS != null) {
6808
+ return {
6809
+ additions: countLines(newS),
6810
+ deletions: countLines(oldS)
6811
+ };
6812
+ }
6813
+ return {};
6814
+ }
6815
+ function parseDiffStat(result) {
6816
+ if (!result) return {};
6817
+ const paired = result.match(/\+(\d+)\s+-(\d+)/);
6818
+ if (paired) {
6819
+ return {
6820
+ additions: Number(paired[1]),
6821
+ deletions: Number(paired[2])
6822
+ };
6823
+ }
6824
+ const verbose = result.match(
6825
+ /(\d+)\s+insertions?(?:,\s*(\d+)\s+deletions?)?/i
6826
+ );
6827
+ if (verbose) {
6828
+ return {
6829
+ additions: Number(verbose[1]),
6830
+ deletions: verbose[2] != null ? Number(verbose[2]) : void 0
6831
+ };
6832
+ }
6833
+ return {};
6834
+ }
6835
+ function applyAgentEvent(parts, event) {
6836
+ if (event.type === "stdout") {
6837
+ const data = event.data;
6838
+ if (!data) return parts;
6839
+ const next = [...parts];
6840
+ const last = next[next.length - 1];
6841
+ if (last?.type === "text" && sameParentId(last.parentId, event.parentId)) {
6842
+ next[next.length - 1] = {
6843
+ type: "text",
6844
+ text: last.text + data,
6845
+ ...event.parentId ? { parentId: event.parentId } : {}
6846
+ };
6847
+ } else {
6848
+ next.push({
6849
+ type: "text",
6850
+ text: data,
6851
+ ...event.parentId ? { parentId: event.parentId } : {}
6852
+ });
6853
+ }
6854
+ return next;
6855
+ }
6856
+ if (event.type === "thinking") {
6857
+ const data = event.data;
6858
+ if (!data) return parts;
6859
+ const next = [...parts];
6860
+ const last = next[next.length - 1];
6861
+ if (last?.type === "thinking" && sameParentId(last.parentId, event.parentId)) {
6862
+ next[next.length - 1] = {
6863
+ type: "thinking",
6864
+ text: last.text + data,
6865
+ ...event.parentId ? { parentId: event.parentId } : {}
6866
+ };
6867
+ } else {
6868
+ next.push({
6869
+ type: "thinking",
6870
+ text: data,
6871
+ ...event.parentId ? { parentId: event.parentId } : {}
6872
+ });
6873
+ }
6874
+ return next;
6875
+ }
6876
+ if (event.type === "tool_use") {
6877
+ const input = asRecord(event.input);
6878
+ const diff = diffFromInput(input);
6879
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
6880
+ if (existing >= 0) {
6881
+ const prev = parts[existing];
6882
+ const next = [...parts];
6883
+ const mergedInput = input && Object.keys(input).length > 0 ? input : prev.input ?? input;
6884
+ next[existing] = {
6885
+ ...prev,
6886
+ name: event.name || prev.name,
6887
+ input: mergedInput,
6888
+ description: toolDescription(event.name || prev.name, mergedInput),
6889
+ detail: toolDetail(event.name || prev.name, mergedInput) ?? prev.detail,
6890
+ filePath: toolFilePath(mergedInput) ?? prev.filePath,
6891
+ additions: diff.additions ?? prev.additions,
6892
+ deletions: diff.deletions ?? prev.deletions,
6893
+ parentId: event.parentId ?? prev.parentId
6894
+ };
6895
+ return next;
6896
+ }
6897
+ return [
6898
+ ...parts,
6899
+ {
6900
+ type: "tool",
6901
+ id: event.id,
6902
+ name: event.name,
6903
+ input,
6904
+ description: toolDescription(event.name, input),
6905
+ detail: toolDetail(event.name, input),
6906
+ status: "running",
6907
+ filePath: toolFilePath(input),
6908
+ ...event.parentId ? { parentId: event.parentId } : {},
6909
+ ...diff
6910
+ }
6911
+ ];
6912
+ }
6913
+ if (event.type === "tool_result") {
6914
+ const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
6915
+ const fromResult = parseDiffStat(event.content);
6916
+ if (existing < 0) {
6917
+ return [
6918
+ ...parts,
6919
+ {
6920
+ type: "tool",
6921
+ id: event.id,
6922
+ name: "tool",
6923
+ description: "tool",
6924
+ status: event.isError ? "error" : "done",
6925
+ result: event.content,
6926
+ ...event.parentId ? { parentId: event.parentId } : {},
6927
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
6928
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
6929
+ }
6930
+ ];
6931
+ }
6932
+ return parts.map((p, i) => {
6933
+ if (i !== existing || p.type !== "tool") return p;
6934
+ return {
6935
+ ...p,
6936
+ status: event.isError ? "error" : "done",
6937
+ result: event.content,
6938
+ ...fromResult.additions != null ? { additions: fromResult.additions } : {},
6939
+ ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
6940
+ };
6941
+ });
6942
+ }
6943
+ return parts;
6944
+ }
6945
+ function partsToAssistantText(parts) {
6946
+ return parts.filter(
6947
+ (p) => p.type === "text" && !messagePartParentId(p)
6948
+ ).map((p) => p.text).join("").trim();
6949
+ }
6950
+ function stripBrightsyNdjsonNoise(text4) {
6951
+ if (!text4 || !text4.includes('"type"')) return text4;
6952
+ let out = text4;
6953
+ out = out.replace(
6954
+ /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
6955
+ ""
6956
+ );
6957
+ out = out.replace(/\{"type":"(?:tool_use|tool_result|tool)"[\s\S]*$/g, "");
6958
+ return out.trim();
6959
+ }
6960
+ function isBrightsyNdjsonLine(line) {
6961
+ const t = line.trim();
6962
+ if (!t.startsWith("{")) return false;
6963
+ return /"type"\s*:\s*"(tool_use|tool_result|tool|thinking|usage|done|error|text)"/.test(t);
6964
+ }
6965
+ function finalizeParts(parts) {
6966
+ return parts.map(
6967
+ (p) => p.type === "tool" && p.status === "running" ? { ...p, status: "done" } : p
6968
+ );
6969
+ }
6970
+ function normalizeParseResult(parsed) {
6971
+ if (!parsed) return [];
6972
+ return Array.isArray(parsed) ? parsed : [parsed];
6973
+ }
6974
+ var init_message_parts = __esm({
6975
+ "src/agents/message-parts.ts"() {
6976
+ "use strict";
6977
+ }
6978
+ });
6979
+
6678
6980
  // src/mcp/profile.ts
6679
6981
  function sideboardMcpProfile(env = process.env) {
6680
6982
  return env[SIDEBOARD_MCP_PROFILE_ENV]?.trim().toLowerCase() === "worktree" ? "worktree" : "orchestration";
@@ -7300,42 +7602,59 @@ function claudeResultErrorDetail(obj) {
7300
7602
  }
7301
7603
  return "Claude turn failed";
7302
7604
  }
7303
- function eventsFromContentBlocks(blocks) {
7605
+ function eventsFromContentBlocks(blocks, parentId) {
7304
7606
  if (!blocks?.length) return [];
7305
7607
  const out = [];
7306
7608
  for (const block of blocks) {
7307
7609
  if (!block?.type) continue;
7308
7610
  if (block.type === "text" && block.text) {
7309
- out.push({ type: "stdout", data: block.text });
7611
+ out.push(withEventParentId({ type: "stdout", data: block.text }, parentId));
7310
7612
  continue;
7311
7613
  }
7312
7614
  if ((block.type === "thinking" || block.type === "redacted_thinking") && block.thinking) {
7313
- out.push({ type: "thinking", data: block.thinking });
7615
+ out.push(withEventParentId({ type: "thinking", data: block.thinking }, parentId));
7314
7616
  continue;
7315
7617
  }
7316
7618
  if (block.type === "tool_use" && block.id && block.name) {
7317
- out.push({
7318
- type: "tool_use",
7319
- id: block.id,
7320
- name: block.name,
7321
- input: block.input
7322
- });
7619
+ out.push(
7620
+ withEventParentId(
7621
+ {
7622
+ type: "tool_use",
7623
+ id: block.id,
7624
+ name: block.name,
7625
+ input: block.input
7626
+ },
7627
+ parentId
7628
+ )
7629
+ );
7323
7630
  continue;
7324
7631
  }
7325
7632
  if (block.type === "tool_result" && block.tool_use_id) {
7326
7633
  const content = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
7327
7634
  (c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c ? String(c.text ?? "") : ""
7328
7635
  ).join("") : block.content != null ? JSON.stringify(block.content) : void 0;
7329
- out.push({
7330
- type: "tool_result",
7331
- id: block.tool_use_id,
7332
- content,
7333
- isError: Boolean(block.is_error)
7334
- });
7636
+ out.push(
7637
+ withEventParentId(
7638
+ {
7639
+ type: "tool_result",
7640
+ id: block.tool_use_id,
7641
+ content,
7642
+ isError: Boolean(block.is_error)
7643
+ },
7644
+ parentId
7645
+ )
7646
+ );
7335
7647
  }
7336
7648
  }
7337
7649
  return out;
7338
7650
  }
7651
+ function claudeParentToolUseId(obj) {
7652
+ const nested = obj.event && typeof obj.event === "object" && !Array.isArray(obj.event) ? obj.event : void 0;
7653
+ for (const id of [obj.parent_tool_use_id, nested?.parent_tool_use_id]) {
7654
+ if (typeof id === "string" && id.trim()) return id.trim();
7655
+ }
7656
+ return void 0;
7657
+ }
7339
7658
  function parseIssuesJson(raw) {
7340
7659
  const text4 = raw.trim();
7341
7660
  const candidates = [text4];
@@ -7370,6 +7689,7 @@ var init_claude = __esm({
7370
7689
  init_app_settings();
7371
7690
  init_claude_mcp();
7372
7691
  init_error_detail();
7692
+ init_message_parts();
7373
7693
  init_injected_mcp();
7374
7694
  init_turn_input();
7375
7695
  init_types();
@@ -7381,7 +7701,10 @@ var init_claude = __esm({
7381
7701
  "Glob",
7382
7702
  "Grep",
7383
7703
  "WebFetch",
7384
- "WebSearch"
7704
+ "WebSearch",
7705
+ // Subagents (Claude Code v2.1.63 renamed Task → Agent; allow both).
7706
+ "Task",
7707
+ "Agent"
7385
7708
  ];
7386
7709
  CLAUDE_CHROME_ALLOWED_TOOLS = [
7387
7710
  "mcp__claude-in-chrome",
@@ -7509,7 +7832,9 @@ var init_claude = __esm({
7509
7832
  args,
7510
7833
  cwd: thread.worktreePath,
7511
7834
  stdin: useStdin ? `${promptText}
7512
- ` : void 0
7835
+ ` : void 0,
7836
+ // Nested Task/Agent thinking+text in stream-json (Claude Code 2.1.211+).
7837
+ env: { CLAUDE_CODE_FORWARD_SUBAGENT_TEXT: "1" }
7513
7838
  };
7514
7839
  },
7515
7840
  parseEvent(line) {
@@ -7526,8 +7851,9 @@ var init_claude = __esm({
7526
7851
  return { type: "session_id", data: obj.session_id };
7527
7852
  }
7528
7853
  if (obj.type === "assistant" || obj.type === "user") {
7854
+ const parentId = claudeParentToolUseId(obj);
7529
7855
  const message = obj.message;
7530
- const events = eventsFromContentBlocks(message?.content);
7856
+ const events = eventsFromContentBlocks(message?.content, parentId);
7531
7857
  if (obj.type === "assistant") {
7532
7858
  const usage = usageFromClaude(message?.usage);
7533
7859
  if (usage) events.push({ type: "usage", data: usage, scope: "request" });
@@ -7536,34 +7862,45 @@ var init_claude = __esm({
7536
7862
  return events.length === 1 ? events[0] : events;
7537
7863
  }
7538
7864
  if (obj.type === "stream_event") {
7865
+ const parentId = claudeParentToolUseId(obj);
7539
7866
  const event = obj.event;
7540
7867
  if (!event) return null;
7541
7868
  if (event.type === "content_block_start" && event.content_block) {
7542
7869
  const block = event.content_block;
7543
7870
  if (block.type === "tool_use" && block.id && block.name) {
7544
- return {
7545
- type: "tool_use",
7546
- id: block.id,
7547
- name: block.name,
7548
- input: block.input
7549
- };
7871
+ return withEventParentId(
7872
+ {
7873
+ type: "tool_use",
7874
+ id: block.id,
7875
+ name: block.name,
7876
+ input: block.input
7877
+ },
7878
+ parentId
7879
+ );
7550
7880
  }
7551
7881
  if (block.type === "thinking" && block.thinking) {
7552
- return { type: "thinking", data: block.thinking };
7882
+ return withEventParentId({ type: "thinking", data: block.thinking }, parentId);
7553
7883
  }
7554
7884
  return null;
7555
7885
  }
7556
7886
  if (event.type === "content_block_delta" && event.delta) {
7557
- if (event.delta.text) return { type: "stdout", data: event.delta.text };
7558
- if (event.delta.thinking) return { type: "thinking", data: event.delta.thinking };
7559
- return null;
7887
+ if (event.delta.text) {
7888
+ return withEventParentId({ type: "stdout", data: event.delta.text }, parentId);
7889
+ }
7890
+ if (event.delta.thinking) {
7891
+ return withEventParentId({ type: "thinking", data: event.delta.thinking }, parentId);
7892
+ }
7893
+ return null;
7560
7894
  }
7561
7895
  return null;
7562
7896
  }
7563
7897
  if (obj.type === "content_block_delta") {
7898
+ const parentId = claudeParentToolUseId(obj);
7564
7899
  const delta = obj.delta;
7565
- if (delta?.text) return { type: "stdout", data: delta.text };
7566
- if (delta?.thinking) return { type: "thinking", data: delta.thinking };
7900
+ if (delta?.text) return withEventParentId({ type: "stdout", data: delta.text }, parentId);
7901
+ if (delta?.thinking) {
7902
+ return withEventParentId({ type: "thinking", data: delta.thinking }, parentId);
7903
+ }
7567
7904
  return null;
7568
7905
  }
7569
7906
  if (obj.type === "result") {
@@ -7696,7 +8033,7 @@ function unwrapCodexMcpResult(result) {
7696
8033
  return String(result);
7697
8034
  }
7698
8035
  }
7699
- function asRecord(value) {
8036
+ function asRecord2(value) {
7700
8037
  if (value && typeof value === "object" && !Array.isArray(value)) {
7701
8038
  return value;
7702
8039
  }
@@ -7861,7 +8198,7 @@ var init_codex = __esm({
7861
8198
  }
7862
8199
  if (itemType === "mcp_tool_call" && item.id && item.server && item.tool) {
7863
8200
  const name = `mcp__${item.server}__${item.tool}`;
7864
- const input = asRecord(item.arguments);
8201
+ const input = asRecord2(item.arguments);
7865
8202
  const events = [
7866
8203
  { type: "tool_use", id: item.id, name, input }
7867
8204
  ];
@@ -7892,6 +8229,36 @@ var init_codex = __esm({
7892
8229
  }
7893
8230
  return events.length === 1 ? events[0] : events;
7894
8231
  }
8232
+ if (itemType === "collab_tool_call" && item.id) {
8233
+ const rawTool = typeof item.tool === "string" ? item.tool : "spawn_agent";
8234
+ const name = /spawn/i.test(rawTool) ? "spawn_agent" : rawTool;
8235
+ const input = {};
8236
+ if (item.prompt) input.prompt = item.prompt;
8237
+ if (Array.isArray(item.receiver_thread_ids)) {
8238
+ input.receiver_thread_ids = item.receiver_thread_ids;
8239
+ }
8240
+ const events = [{ type: "tool_use", id: item.id, name, input }];
8241
+ const finished = type === "item.completed" || item.status === "completed" || item.status === "failed";
8242
+ if (finished) {
8243
+ const nested = [];
8244
+ if (item.agents_states && typeof item.agents_states === "object") {
8245
+ for (const value of Object.values(item.agents_states)) {
8246
+ const msg = value && typeof value === "object" && typeof value.message === "string" ? value.message.trim() : "";
8247
+ if (msg) nested.push(msg);
8248
+ }
8249
+ }
8250
+ for (const text4 of nested) {
8251
+ events.push({ type: "stdout", data: text4, parentId: item.id });
8252
+ }
8253
+ events.push({
8254
+ type: "tool_result",
8255
+ id: item.id,
8256
+ content: nested.join("\n") || void 0,
8257
+ isError: item.status === "failed"
8258
+ });
8259
+ }
8260
+ return events.length === 1 ? events[0] : events;
8261
+ }
7895
8262
  if (item.status === "failed") {
7896
8263
  const detail = item.message?.trim() || extractJsonErrorMessage(item) || `Codex ${itemType ?? "item"} failed`;
7897
8264
  return { type: "stderr", data: detail };
@@ -7980,20 +8347,20 @@ function usageFromCursor(usage) {
7980
8347
  cacheWriteTokens: usage.cacheWriteTokens ? Number(usage.cacheWriteTokens) : void 0
7981
8348
  };
7982
8349
  }
7983
- function asRecord2(value) {
8350
+ function asRecord3(value) {
7984
8351
  if (value && typeof value === "object" && !Array.isArray(value)) {
7985
8352
  return value;
7986
8353
  }
7987
8354
  return void 0;
7988
8355
  }
7989
8356
  function normalizeCursorToolCall(name, args) {
7990
- const raw = asRecord2(args);
8357
+ const raw = asRecord3(args);
7991
8358
  const toolName = typeof raw?.toolName === "string" && raw.toolName.trim() ? raw.toolName.trim() : null;
7992
- const looksLikeMcp = name === "mcp" || toolName != null && (typeof raw?.providerIdentifier === "string" || asRecord2(raw?.args) != null);
8359
+ const looksLikeMcp = name === "mcp" || toolName != null && (typeof raw?.providerIdentifier === "string" || asRecord3(raw?.args) != null);
7993
8360
  if (looksLikeMcp && toolName) {
7994
8361
  const providerRaw = typeof raw.providerIdentifier === "string" && raw.providerIdentifier.trim() ? raw.providerIdentifier.trim() : "sideboard";
7995
8362
  const provider = providerRaw.replace(/[^a-zA-Z0-9_-]/g, "_");
7996
- const nested = asRecord2(raw.args);
8363
+ const nested = asRecord3(raw.args);
7997
8364
  return {
7998
8365
  name: `mcp__${provider}__${toolName}`,
7999
8366
  input: nested ?? { toolName }
@@ -8004,7 +8371,7 @@ function normalizeCursorToolCall(name, args) {
8004
8371
  function unwrapCursorToolResult(result) {
8005
8372
  if (result == null) return void 0;
8006
8373
  if (typeof result === "string") return result;
8007
- const rec = asRecord2(result);
8374
+ const rec = asRecord3(result);
8008
8375
  if (!rec) {
8009
8376
  try {
8010
8377
  return JSON.stringify(result);
@@ -8015,18 +8382,18 @@ function unwrapCursorToolResult(result) {
8015
8382
  const collectTexts = (items) => {
8016
8383
  const texts = [];
8017
8384
  for (const item of items) {
8018
- const row = asRecord2(item);
8385
+ const row = asRecord3(item);
8019
8386
  if (!row) continue;
8020
8387
  if (typeof row.text === "string") {
8021
8388
  texts.push(row.text);
8022
8389
  continue;
8023
8390
  }
8024
- const nested = asRecord2(row.text);
8391
+ const nested = asRecord3(row.text);
8025
8392
  if (typeof nested?.text === "string") texts.push(nested.text);
8026
8393
  }
8027
8394
  return texts;
8028
8395
  };
8029
- const value = asRecord2(rec.value);
8396
+ const value = asRecord3(rec.value);
8030
8397
  if (value && Array.isArray(value.content)) {
8031
8398
  const texts = collectTexts(value.content);
8032
8399
  if (texts.length) return texts.join("\n");
@@ -8043,10 +8410,10 @@ function unwrapCursorToolResult(result) {
8043
8410
  }
8044
8411
  function cursorToolResultIsError(status, result) {
8045
8412
  if (status === "error") return true;
8046
- const rec = asRecord2(result);
8413
+ const rec = asRecord3(result);
8047
8414
  if (!rec) return false;
8048
8415
  if (rec.status === "error") return true;
8049
- const value = asRecord2(rec.value);
8416
+ const value = asRecord3(rec.value);
8050
8417
  return value?.isError === true;
8051
8418
  }
8052
8419
  function cursorSdkMessageToEvents(msg) {
@@ -8389,6 +8756,24 @@ var init_cursor = __esm({
8389
8756
  });
8390
8757
 
8391
8758
  // src/agents/opencode.ts
8759
+ function asRecord4(value) {
8760
+ if (value && typeof value === "object" && !Array.isArray(value)) {
8761
+ return value;
8762
+ }
8763
+ return void 0;
8764
+ }
8765
+ function str3(v) {
8766
+ return typeof v === "string" && v.trim() ? v.trim() : void 0;
8767
+ }
8768
+ function opencodeToolId(name, callId, metadata) {
8769
+ const sessionId = str3(metadata?.sessionId) ?? str3(metadata?.sessionID);
8770
+ if (name === "task" && sessionId) return sessionId;
8771
+ return callId || `tool-${Date.now()}`;
8772
+ }
8773
+ function withTaskParent(parentId, nested) {
8774
+ const events = Array.isArray(nested) ? nested : [nested];
8775
+ return [{ type: "tool_use", id: parentId, name: "task" }, ...events];
8776
+ }
8392
8777
  function displayNameFromOpencodeId(id) {
8393
8778
  const slash = id.indexOf("/");
8394
8779
  if (slash <= 0) return id;
@@ -8455,6 +8840,7 @@ var init_opencode = __esm({
8455
8840
  init_app_settings();
8456
8841
  init_global_workspace();
8457
8842
  init_error_detail();
8843
+ init_message_parts();
8458
8844
  init_injected_mcp();
8459
8845
  init_turn_input();
8460
8846
  init_types();
@@ -8571,10 +8957,15 @@ var init_opencode = __esm({
8571
8957
  }
8572
8958
  if (obj.type === "tool_use") {
8573
8959
  const part = obj.part;
8574
- const id = part?.callID ?? part?.id ?? obj.id ?? `tool-${Date.now()}`;
8575
8960
  const name = part?.tool ?? part?.name ?? obj.tool ?? obj.name ?? "tool";
8576
8961
  const state = part?.state;
8577
8962
  const input = (state?.input && typeof state.input === "object" ? state.input : void 0) ?? part?.input ?? obj.input;
8963
+ const metadata = asRecord4(state?.metadata);
8964
+ const id = opencodeToolId(
8965
+ name,
8966
+ part?.callID ?? part?.id ?? obj.id,
8967
+ metadata
8968
+ );
8578
8969
  const events = [{ type: "tool_use", id, name, input }];
8579
8970
  const output = state?.output;
8580
8971
  if (output != null || state?.status === "completed" || state?.status === "error") {
@@ -8588,6 +8979,53 @@ var init_opencode = __esm({
8588
8979
  }
8589
8980
  return events.length === 1 ? events[0] : events;
8590
8981
  }
8982
+ if (obj.type === "subtask_delta") {
8983
+ const parentId = str3(obj.childSessionID) ?? str3(obj.childSessionId);
8984
+ const delta = str3(obj.delta);
8985
+ if (parentId && delta) {
8986
+ return withTaskParent(
8987
+ parentId,
8988
+ withEventParentId({ type: "thinking", data: delta }, parentId)
8989
+ );
8990
+ }
8991
+ return null;
8992
+ }
8993
+ if (obj.type === "subtask_event") {
8994
+ const parentId = str3(obj.childSessionID) ?? str3(obj.childSessionId);
8995
+ const part = asRecord4(obj.part);
8996
+ if (!parentId || !part) return null;
8997
+ if (part.type === "text" && str3(part.text)) {
8998
+ return withTaskParent(
8999
+ parentId,
9000
+ withEventParentId({ type: "stdout", data: str3(part.text) }, parentId)
9001
+ );
9002
+ }
9003
+ if (part.type === "tool" || str3(part.tool)) {
9004
+ const name = str3(part.tool) ?? str3(part.name) ?? "tool";
9005
+ const id = str3(part.callID) ?? str3(part.id) ?? `${parentId}-${name}`;
9006
+ const state = asRecord4(part.state);
9007
+ const input = asRecord4(state?.input) ?? asRecord4(part.input);
9008
+ const nested = [
9009
+ withEventParentId({ type: "tool_use", id, name, input }, parentId)
9010
+ ];
9011
+ const output = state?.output ?? part.output;
9012
+ if (output != null || state?.status === "completed" || state?.status === "error") {
9013
+ nested.push(
9014
+ withEventParentId(
9015
+ {
9016
+ type: "tool_result",
9017
+ id,
9018
+ content: typeof output === "string" ? output : output != null ? JSON.stringify(output) : void 0,
9019
+ isError: state?.status === "error" || state?.status === "failed"
9020
+ },
9021
+ parentId
9022
+ )
9023
+ );
9024
+ }
9025
+ return withTaskParent(parentId, nested);
9026
+ }
9027
+ return null;
9028
+ }
8591
9029
  if (obj.type === "tool_result") {
8592
9030
  const part = obj.part;
8593
9031
  const id = part?.tool_use_id ?? part?.id ?? obj.tool_use_id ?? obj.id;
@@ -9015,424 +9453,191 @@ async function installAgent(agent) {
9015
9453
  stderr: result.stderr,
9016
9454
  message: result.stderr.trim() || result.stdout.trim() || `npm install failed (exit ${result.exitCode})`
9017
9455
  };
9018
- }
9019
- }
9020
- enrichPathWithNpmGlobalBin();
9021
- let binPath = null;
9022
- if (cliBin) {
9023
- binPath = await whichCli(cliBin);
9024
- }
9025
- return {
9026
- ok: true,
9027
- command: info.installCommand,
9028
- exitCode: 0,
9029
- stdout: result.stdout,
9030
- stderr: result.stderr,
9031
- message: binPath ? `Installed ${info.npmPackage} \u2192 ${binPath}` : `Installed ${info.npmPackage} (CLI not yet on PATH \u2014 Log in still exports Electron\u2019s PATH into Terminal)`
9032
- };
9033
- }
9034
- async function loginAgent(agent) {
9035
- const info = getAgentSetupInfo(agent);
9036
- const loginCommand = resolveLoginCommand(agent);
9037
- if (!loginCommand) {
9038
- return {
9039
- ok: true,
9040
- message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
9041
- };
9042
- }
9043
- const prepared = await prepareTerminalCommand(loginCommand);
9044
- await openInSystemTerminal(prepared);
9045
- return {
9046
- ok: true,
9047
- openedTerminal: true,
9048
- command: prepared,
9049
- message: `Opened Terminal to run: ${prepared}`
9050
- };
9051
- }
9052
- var SETUP, CLI_BIN, CLI_LOGIN_AGENTS;
9053
- var init_install = __esm({
9054
- "src/agents/install.ts"() {
9055
- "use strict";
9056
- init_run();
9057
- init_app_settings();
9058
- init_path();
9059
- SETUP = {
9060
- claude: {
9061
- agent: "claude",
9062
- kind: "cli",
9063
- summary: "Install the Claude Code CLI, then complete login in a terminal.",
9064
- docsUrl: "https://code.claude.com/docs/en/install",
9065
- installCommand: "npm install -g @anthropic-ai/claude-code",
9066
- loginCommand: "claude auth login",
9067
- npmPackage: "@anthropic-ai/claude-code"
9068
- },
9069
- codex: {
9070
- agent: "codex",
9071
- kind: "cli",
9072
- summary: "Install the Codex CLI, then run login in a terminal.",
9073
- docsUrl: "https://github.com/openai/codex",
9074
- installCommand: "npm install -g @openai/codex",
9075
- loginCommand: "codex login",
9076
- npmPackage: "@openai/codex"
9077
- },
9078
- opencode: {
9079
- agent: "opencode",
9080
- kind: "cli",
9081
- summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
9082
- docsUrl: "https://opencode.ai/docs",
9083
- // Prefer the official installer; npm package name is opencode-ai.
9084
- installCommand: "curl -fsSL https://opencode.ai/install | bash",
9085
- loginCommand: "opencode auth login",
9086
- npmPackage: "opencode-ai@latest"
9087
- },
9088
- cursor: {
9089
- agent: "cursor",
9090
- kind: "bundled-sdk",
9091
- summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
9092
- docsUrl: "https://cursor.com/dashboard/integrations",
9093
- installCommand: null,
9094
- loginCommand: null
9095
- },
9096
- brightsy: {
9097
- agent: "brightsy",
9098
- kind: "cli",
9099
- summary: "Install the Brightsy CLI, then run `brightsy login`.",
9100
- docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
9101
- installCommand: "npm install -g @brightsy/cli",
9102
- loginCommand: "brightsy login",
9103
- npmPackage: "@brightsy/cli"
9104
- }
9105
- };
9106
- CLI_BIN = {
9107
- claude: "claude",
9108
- codex: "codex",
9109
- opencode: "opencode",
9110
- brightsy: "brightsy"
9111
- };
9112
- CLI_LOGIN_AGENTS = /* @__PURE__ */ new Set([
9113
- "claude",
9114
- "codex",
9115
- "opencode",
9116
- "brightsy"
9117
- ]);
9118
- }
9119
- });
9120
-
9121
- // src/agents/index.ts
9122
- var agents_exports = {};
9123
- __export(agents_exports, {
9124
- CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
9125
- ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
9126
- PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
9127
- allAdapters: () => allAdapters,
9128
- assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
9129
- brightsyAdapter: () => brightsyAdapter,
9130
- claudeAdapter: () => claudeAdapter,
9131
- codexAdapter: () => codexAdapter,
9132
- coerceOrchestratorAgent: () => coerceOrchestratorAgent,
9133
- conductorBundledBinDir: () => conductorBundledBinDir,
9134
- cursorAdapter: () => cursorAdapter,
9135
- cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
9136
- decodeBrightsyTarget: () => decodeBrightsyTarget,
9137
- encodeBrightsyTarget: () => encodeBrightsyTarget,
9138
- enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
9139
- ensureAgentPath: () => ensureAgentPath,
9140
- getAdapter: () => getAdapter,
9141
- getAgentSetupInfo: () => getAgentSetupInfo,
9142
- installAgent: () => installAgent,
9143
- isConductorBundledCli: () => isConductorBundledCli,
9144
- isCursorAutoModel: () => isCursorAutoModel,
9145
- isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
9146
- isSessionQuotaLimit: () => isSessionQuotaLimit,
9147
- listAgentSetupInfo: () => listAgentSetupInfo,
9148
- listBrightsyChatTargets: () => listBrightsyChatTargets,
9149
- listCodexModels: () => listCodexModels,
9150
- listCursorModels: () => listCursorModels,
9151
- listModelsForAgent: () => listModelsForAgent,
9152
- listOpencodeModels: () => listOpencodeModels,
9153
- loginAgent: () => loginAgent,
9154
- openInSystemTerminal: () => openInSystemTerminal,
9155
- opencodeAdapter: () => opencodeAdapter,
9156
- parseCursorRunnerLine: () => parseCursorRunnerLine,
9157
- parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
9158
- permissionMode: () => permissionMode,
9159
- posixShellSingleQuote: () => posixShellSingleQuote,
9160
- prepareTerminalCommand: () => prepareTerminalCommand,
9161
- resolveCommandBinarySync: () => resolveCommandBinarySync,
9162
- resolveCursorModelId: () => resolveCursorModelId,
9163
- resolveLoginCommand: () => resolveLoginCommand,
9164
- resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
9165
- withExportedPath: () => withExportedPath
9166
- });
9167
- function getAdapter(kind) {
9168
- return adapters[kind];
9169
- }
9170
- function allAdapters() {
9171
- return Object.values(adapters);
9172
- }
9173
- var adapters;
9174
- var init_agents = __esm({
9175
- "src/agents/index.ts"() {
9176
- "use strict";
9177
- init_brightsy();
9178
- init_claude();
9179
- init_codex();
9180
- init_cursor();
9181
- init_opencode();
9182
- init_types();
9183
- init_brightsy();
9184
- init_brightsy_targets();
9185
- init_claude();
9186
- init_codex();
9187
- init_cursor();
9188
- init_cursor_events();
9189
- init_cursor();
9190
- init_opencode();
9191
- init_list_models();
9192
- init_session_quota();
9193
- init_orchestrator_capable();
9194
- init_path();
9195
- init_install();
9196
- adapters = {
9197
- claude: claudeAdapter,
9198
- codex: codexAdapter,
9199
- opencode: opencodeAdapter,
9200
- brightsy: brightsyAdapter,
9201
- cursor: cursorAdapter
9202
- };
9203
- }
9204
- });
9205
-
9206
- // src/agents/message-parts.ts
9207
- function asRecord3(input) {
9208
- if (input && typeof input === "object" && !Array.isArray(input)) {
9209
- return input;
9210
- }
9211
- return void 0;
9212
- }
9213
- function str2(v) {
9214
- return typeof v === "string" && v.trim() ? v : void 0;
9215
- }
9216
- function toolDetail(name, input) {
9217
- if (!input) return void 0;
9218
- const command = str2(input.command) ?? str2(input.cmd);
9219
- if (command) return command;
9220
- const path = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9221
- if (path) return path;
9222
- const pattern = str2(input.pattern) ?? str2(input.glob) ?? str2(input.glob_pattern);
9223
- if (pattern) return pattern;
9224
- const query = str2(input.query) ?? str2(input.prompt);
9225
- if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
9226
- try {
9227
- const raw = JSON.stringify(input);
9228
- return raw.length > 80 ? `${raw.slice(0, 77)}\u2026` : raw;
9229
- } catch {
9230
- return name;
9231
- }
9232
- }
9233
- function toolDescription(name, input) {
9234
- const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
9235
- if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
9236
- if (/connectedAgentRequest/i.test(name)) {
9237
- return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
9238
- }
9239
- if (/present_artifact$/i.test(name)) {
9240
- return str2(input?.title) ? `Present ${str2(input?.title)}` : "Present artifact";
9241
- }
9242
- if (/present_plan$/i.test(name)) {
9243
- return str2(input?.title) ? `Plan ${str2(input?.title)}` : "Present plan";
9244
- }
9245
- if (/present_schema$/i.test(name)) {
9246
- return str2(input?.title) ? `Schema ${str2(input?.title)}` : "Present schema";
9247
- }
9248
- if (/present_files$/i.test(name)) {
9249
- return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
9250
- }
9251
- if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
9252
- return str2(input?.title) ? `Artifact ${str2(input?.title)}` : "Artifact";
9253
- }
9254
- if (!input) return n;
9255
- if (/bash|shell|terminal/i.test(name) && str2(input.command)) {
9256
- const cmd = str2(input.command);
9257
- if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
9258
- if (/git\s+status/i.test(cmd)) return "Check git status";
9259
- if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
9260
- if (/git\s+branch/i.test(cmd)) return "List branches";
9261
- if (/grep|rg\b/i.test(cmd)) return "Search repository";
9262
- return "Run shell command";
9263
- }
9264
- if (/edit|write|apply/i.test(name)) {
9265
- const path = str2(input.file_path) ?? str2(input.path);
9266
- return path ? `Edit ${path.split("/").pop()}` : "Edit file";
9267
- }
9268
- if (/read/i.test(name)) {
9269
- const path = str2(input.file_path) ?? str2(input.path);
9270
- return path ? `Read ${path.split("/").pop()}` : "Read file";
9271
- }
9272
- if (/grep/i.test(name)) return "Search files";
9273
- if (/glob/i.test(name)) return "Find files";
9274
- return n;
9275
- }
9276
- function toolFilePath(input) {
9277
- if (!input) return void 0;
9278
- return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
9279
- }
9280
- function countLines(text4) {
9281
- if (!text4) return 0;
9282
- return text4.split("\n").length;
9283
- }
9284
- function diffFromInput(input) {
9285
- if (!input) return {};
9286
- const oldS = str2(input.old_string) ?? str2(input.oldString);
9287
- const newS = str2(input.new_string) ?? str2(input.newString) ?? str2(input.content);
9288
- if (oldS != null || newS != null) {
9289
- return {
9290
- additions: countLines(newS),
9291
- deletions: countLines(oldS)
9292
- };
9293
- }
9294
- return {};
9295
- }
9296
- function parseDiffStat(result) {
9297
- if (!result) return {};
9298
- const paired = result.match(/\+(\d+)\s+-(\d+)/);
9299
- if (paired) {
9300
- return {
9301
- additions: Number(paired[1]),
9302
- deletions: Number(paired[2])
9303
- };
9304
- }
9305
- const verbose = result.match(
9306
- /(\d+)\s+insertions?(?:,\s*(\d+)\s+deletions?)?/i
9307
- );
9308
- if (verbose) {
9309
- return {
9310
- additions: Number(verbose[1]),
9311
- deletions: verbose[2] != null ? Number(verbose[2]) : void 0
9312
- };
9313
- }
9314
- return {};
9315
- }
9316
- function applyAgentEvent(parts, event) {
9317
- if (event.type === "stdout") {
9318
- const data = event.data;
9319
- if (!data) return parts;
9320
- const next = [...parts];
9321
- const last = next[next.length - 1];
9322
- if (last?.type === "text") {
9323
- next[next.length - 1] = { type: "text", text: last.text + data };
9324
- } else {
9325
- next.push({ type: "text", text: data });
9326
- }
9327
- return next;
9328
- }
9329
- if (event.type === "thinking") {
9330
- const data = event.data;
9331
- if (!data) return parts;
9332
- const next = [...parts];
9333
- const last = next[next.length - 1];
9334
- if (last?.type === "thinking") {
9335
- next[next.length - 1] = { type: "thinking", text: last.text + data };
9336
- } else {
9337
- next.push({ type: "thinking", text: data });
9338
- }
9339
- return next;
9340
- }
9341
- if (event.type === "tool_use") {
9342
- const input = asRecord3(event.input);
9343
- const diff = diffFromInput(input);
9344
- const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
9345
- if (existing >= 0) {
9346
- const prev = parts[existing];
9347
- const next = [...parts];
9348
- next[existing] = {
9349
- ...prev,
9350
- name: event.name || prev.name,
9351
- input: input ?? prev.input,
9352
- description: toolDescription(event.name || prev.name, input ?? prev.input),
9353
- detail: toolDetail(event.name || prev.name, input ?? prev.input) ?? prev.detail,
9354
- filePath: toolFilePath(input) ?? prev.filePath,
9355
- additions: diff.additions ?? prev.additions,
9356
- deletions: diff.deletions ?? prev.deletions
9357
- };
9358
- return next;
9359
- }
9360
- return [
9361
- ...parts,
9362
- {
9363
- type: "tool",
9364
- id: event.id,
9365
- name: event.name,
9366
- input,
9367
- description: toolDescription(event.name, input),
9368
- detail: toolDetail(event.name, input),
9369
- status: "running",
9370
- filePath: toolFilePath(input),
9371
- ...diff
9372
- }
9373
- ];
9374
- }
9375
- if (event.type === "tool_result") {
9376
- const existing = parts.findIndex((p) => p.type === "tool" && p.id === event.id);
9377
- const fromResult = parseDiffStat(event.content);
9378
- if (existing < 0) {
9379
- return [
9380
- ...parts,
9381
- {
9382
- type: "tool",
9383
- id: event.id,
9384
- name: "tool",
9385
- description: "tool",
9386
- status: event.isError ? "error" : "done",
9387
- result: event.content,
9388
- ...fromResult.additions != null ? { additions: fromResult.additions } : {},
9389
- ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
9390
- }
9391
- ];
9392
- }
9393
- return parts.map((p, i) => {
9394
- if (i !== existing || p.type !== "tool") return p;
9395
- return {
9396
- ...p,
9397
- status: event.isError ? "error" : "done",
9398
- result: event.content,
9399
- ...fromResult.additions != null ? { additions: fromResult.additions } : {},
9400
- ...fromResult.deletions != null ? { deletions: fromResult.deletions } : {}
9401
- };
9402
- });
9456
+ }
9403
9457
  }
9404
- return parts;
9405
- }
9406
- function partsToAssistantText(parts) {
9407
- return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
9408
- }
9409
- function stripBrightsyNdjsonNoise(text4) {
9410
- if (!text4 || !text4.includes('"type"')) return text4;
9411
- let out = text4;
9412
- out = out.replace(
9413
- /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
9414
- ""
9415
- );
9416
- out = out.replace(/\{"type":"(?:tool_use|tool_result|tool)"[\s\S]*$/g, "");
9417
- return out.trim();
9458
+ enrichPathWithNpmGlobalBin();
9459
+ let binPath = null;
9460
+ if (cliBin) {
9461
+ binPath = await whichCli(cliBin);
9462
+ }
9463
+ return {
9464
+ ok: true,
9465
+ command: info.installCommand,
9466
+ exitCode: 0,
9467
+ stdout: result.stdout,
9468
+ stderr: result.stderr,
9469
+ message: binPath ? `Installed ${info.npmPackage} \u2192 ${binPath}` : `Installed ${info.npmPackage} (CLI not yet on PATH \u2014 Log in still exports Electron\u2019s PATH into Terminal)`
9470
+ };
9418
9471
  }
9419
- function isBrightsyNdjsonLine(line) {
9420
- const t = line.trim();
9421
- if (!t.startsWith("{")) return false;
9422
- return /"type"\s*:\s*"(tool_use|tool_result|tool|thinking|usage|done|error|text)"/.test(t);
9472
+ async function loginAgent(agent) {
9473
+ const info = getAgentSetupInfo(agent);
9474
+ const loginCommand = resolveLoginCommand(agent);
9475
+ if (!loginCommand) {
9476
+ return {
9477
+ ok: true,
9478
+ message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
9479
+ };
9480
+ }
9481
+ const prepared = await prepareTerminalCommand(loginCommand);
9482
+ await openInSystemTerminal(prepared);
9483
+ return {
9484
+ ok: true,
9485
+ openedTerminal: true,
9486
+ command: prepared,
9487
+ message: `Opened Terminal to run: ${prepared}`
9488
+ };
9423
9489
  }
9424
- function finalizeParts(parts) {
9425
- return parts.map(
9426
- (p) => p.type === "tool" && p.status === "running" ? { ...p, status: "done" } : p
9427
- );
9490
+ var SETUP, CLI_BIN, CLI_LOGIN_AGENTS;
9491
+ var init_install = __esm({
9492
+ "src/agents/install.ts"() {
9493
+ "use strict";
9494
+ init_run();
9495
+ init_app_settings();
9496
+ init_path();
9497
+ SETUP = {
9498
+ claude: {
9499
+ agent: "claude",
9500
+ kind: "cli",
9501
+ summary: "Install the Claude Code CLI, then complete login in a terminal.",
9502
+ docsUrl: "https://code.claude.com/docs/en/install",
9503
+ installCommand: "npm install -g @anthropic-ai/claude-code",
9504
+ loginCommand: "claude auth login",
9505
+ npmPackage: "@anthropic-ai/claude-code"
9506
+ },
9507
+ codex: {
9508
+ agent: "codex",
9509
+ kind: "cli",
9510
+ summary: "Install the Codex CLI, then run login in a terminal.",
9511
+ docsUrl: "https://github.com/openai/codex",
9512
+ installCommand: "npm install -g @openai/codex",
9513
+ loginCommand: "codex login",
9514
+ npmPackage: "@openai/codex"
9515
+ },
9516
+ opencode: {
9517
+ agent: "opencode",
9518
+ kind: "cli",
9519
+ summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
9520
+ docsUrl: "https://opencode.ai/docs",
9521
+ // Prefer the official installer; npm package name is opencode-ai.
9522
+ installCommand: "curl -fsSL https://opencode.ai/install | bash",
9523
+ loginCommand: "opencode auth login",
9524
+ npmPackage: "opencode-ai@latest"
9525
+ },
9526
+ cursor: {
9527
+ agent: "cursor",
9528
+ kind: "bundled-sdk",
9529
+ summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
9530
+ docsUrl: "https://cursor.com/dashboard/integrations",
9531
+ installCommand: null,
9532
+ loginCommand: null
9533
+ },
9534
+ brightsy: {
9535
+ agent: "brightsy",
9536
+ kind: "cli",
9537
+ summary: "Install the Brightsy CLI, then run `brightsy login`.",
9538
+ docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
9539
+ installCommand: "npm install -g @brightsy/cli",
9540
+ loginCommand: "brightsy login",
9541
+ npmPackage: "@brightsy/cli"
9542
+ }
9543
+ };
9544
+ CLI_BIN = {
9545
+ claude: "claude",
9546
+ codex: "codex",
9547
+ opencode: "opencode",
9548
+ brightsy: "brightsy"
9549
+ };
9550
+ CLI_LOGIN_AGENTS = /* @__PURE__ */ new Set([
9551
+ "claude",
9552
+ "codex",
9553
+ "opencode",
9554
+ "brightsy"
9555
+ ]);
9556
+ }
9557
+ });
9558
+
9559
+ // src/agents/index.ts
9560
+ var agents_exports = {};
9561
+ __export(agents_exports, {
9562
+ CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
9563
+ ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
9564
+ PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
9565
+ allAdapters: () => allAdapters,
9566
+ assertOrchestratorCapableAgent: () => assertOrchestratorCapableAgent,
9567
+ brightsyAdapter: () => brightsyAdapter,
9568
+ claudeAdapter: () => claudeAdapter,
9569
+ codexAdapter: () => codexAdapter,
9570
+ coerceOrchestratorAgent: () => coerceOrchestratorAgent,
9571
+ conductorBundledBinDir: () => conductorBundledBinDir,
9572
+ cursorAdapter: () => cursorAdapter,
9573
+ cursorSdkMessageToEvents: () => cursorSdkMessageToEvents,
9574
+ decodeBrightsyTarget: () => decodeBrightsyTarget,
9575
+ encodeBrightsyTarget: () => encodeBrightsyTarget,
9576
+ enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
9577
+ ensureAgentPath: () => ensureAgentPath,
9578
+ getAdapter: () => getAdapter,
9579
+ getAgentSetupInfo: () => getAgentSetupInfo,
9580
+ installAgent: () => installAgent,
9581
+ isConductorBundledCli: () => isConductorBundledCli,
9582
+ isCursorAutoModel: () => isCursorAutoModel,
9583
+ isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
9584
+ isSessionQuotaLimit: () => isSessionQuotaLimit,
9585
+ listAgentSetupInfo: () => listAgentSetupInfo,
9586
+ listBrightsyChatTargets: () => listBrightsyChatTargets,
9587
+ listCodexModels: () => listCodexModels,
9588
+ listCursorModels: () => listCursorModels,
9589
+ listModelsForAgent: () => listModelsForAgent,
9590
+ listOpencodeModels: () => listOpencodeModels,
9591
+ loginAgent: () => loginAgent,
9592
+ openInSystemTerminal: () => openInSystemTerminal,
9593
+ opencodeAdapter: () => opencodeAdapter,
9594
+ parseCursorRunnerLine: () => parseCursorRunnerLine,
9595
+ parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
9596
+ permissionMode: () => permissionMode,
9597
+ posixShellSingleQuote: () => posixShellSingleQuote,
9598
+ prepareTerminalCommand: () => prepareTerminalCommand,
9599
+ resolveCommandBinarySync: () => resolveCommandBinarySync,
9600
+ resolveCursorModelId: () => resolveCursorModelId,
9601
+ resolveLoginCommand: () => resolveLoginCommand,
9602
+ resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
9603
+ withExportedPath: () => withExportedPath
9604
+ });
9605
+ function getAdapter(kind) {
9606
+ return adapters[kind];
9428
9607
  }
9429
- function normalizeParseResult(parsed) {
9430
- if (!parsed) return [];
9431
- return Array.isArray(parsed) ? parsed : [parsed];
9608
+ function allAdapters() {
9609
+ return Object.values(adapters);
9432
9610
  }
9433
- var init_message_parts = __esm({
9434
- "src/agents/message-parts.ts"() {
9611
+ var adapters;
9612
+ var init_agents = __esm({
9613
+ "src/agents/index.ts"() {
9435
9614
  "use strict";
9615
+ init_brightsy();
9616
+ init_claude();
9617
+ init_codex();
9618
+ init_cursor();
9619
+ init_opencode();
9620
+ init_types();
9621
+ init_brightsy();
9622
+ init_brightsy_targets();
9623
+ init_claude();
9624
+ init_codex();
9625
+ init_cursor();
9626
+ init_cursor_events();
9627
+ init_cursor();
9628
+ init_opencode();
9629
+ init_list_models();
9630
+ init_session_quota();
9631
+ init_orchestrator_capable();
9632
+ init_path();
9633
+ init_install();
9634
+ adapters = {
9635
+ claude: claudeAdapter,
9636
+ codex: codexAdapter,
9637
+ opencode: opencodeAdapter,
9638
+ brightsy: brightsyAdapter,
9639
+ cursor: cursorAdapter
9640
+ };
9436
9641
  }
9437
9642
  });
9438
9643
 
@@ -9440,6 +9645,9 @@ var init_message_parts = __esm({
9440
9645
  function isTextEvent(event) {
9441
9646
  return event.type === "stdout" || event.type === "thinking";
9442
9647
  }
9648
+ function parentKey(event) {
9649
+ return event.parentId ?? "";
9650
+ }
9443
9651
  function createAgentStreamCoalescer(emit, opts) {
9444
9652
  const intervalMs = opts?.intervalMs ?? 32;
9445
9653
  let pending = null;
@@ -9452,7 +9660,11 @@ function createAgentStreamCoalescer(emit, opts) {
9452
9660
  if (!pending) return;
9453
9661
  const event = pending;
9454
9662
  pending = null;
9455
- emit({ type: event.type, data: event.data });
9663
+ emit({
9664
+ type: event.type,
9665
+ data: event.data,
9666
+ ...event.parentId ? { parentId: event.parentId } : {}
9667
+ });
9456
9668
  };
9457
9669
  const schedule = () => {
9458
9670
  if (timer) return;
@@ -9467,11 +9679,15 @@ function createAgentStreamCoalescer(emit, opts) {
9467
9679
  return;
9468
9680
  }
9469
9681
  if (!event.data) return;
9470
- if (pending && pending.type === event.type) {
9682
+ if (pending && pending.type === event.type && parentKey(pending) === parentKey(event)) {
9471
9683
  pending.data += event.data;
9472
9684
  } else {
9473
9685
  flush2();
9474
- pending = { type: event.type, data: event.data };
9686
+ pending = {
9687
+ type: event.type,
9688
+ data: event.data,
9689
+ ...event.parentId ? { parentId: event.parentId } : {}
9690
+ };
9475
9691
  }
9476
9692
  schedule();
9477
9693
  },
@@ -10101,7 +10317,8 @@ function worktreeBindingFrom(from) {
10101
10317
  prState: from.prState,
10102
10318
  skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
10103
10319
  stackId: from.stackId,
10104
- stackLayer: from.stackLayer
10320
+ stackLayer: from.stackLayer,
10321
+ cowboy: from.cowboy
10105
10322
  };
10106
10323
  }
10107
10324
  function threadsSharingWorktree(worktreePath) {
@@ -11294,6 +11511,55 @@ async function createThread(input, _onSetupLine) {
11294
11511
  if (!(0, import_node_fs32.existsSync)(repoPath)) {
11295
11512
  throw new Error(`Repo not found: ${repoPath}`);
11296
11513
  }
11514
+ if (input.cowboy) {
11515
+ const { cowboyModeEnabled: cowboyModeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
11516
+ if (!cowboyModeEnabled2()) {
11517
+ throw new Error(
11518
+ "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
11519
+ );
11520
+ }
11521
+ if (input.sourceType === "pr" || input.sourceType === "ticket" || input.sourceType === "adopt") {
11522
+ throw new Error(
11523
+ "Cowboy mode works on the project folder (default branch), not a PR, ticket, or adopt."
11524
+ );
11525
+ }
11526
+ const defaultBranch = await resolveDefaultBranch(repoPath, { network: false });
11527
+ const head = await currentBranch(repoPath);
11528
+ if (head === "HEAD") {
11529
+ throw new Error(
11530
+ "Cowboy mode needs a named branch in the project folder (not a detached HEAD)."
11531
+ );
11532
+ }
11533
+ if (head !== defaultBranch) {
11534
+ throw new Error(
11535
+ `Cowboy mode uses ${defaultBranch} in the project folder. Switch that checkout to ${defaultBranch} first (currently ${head}).`
11536
+ );
11537
+ }
11538
+ const explicitTitle2 = input.title?.trim();
11539
+ const thread2 = createEmptyThread({
11540
+ title: explicitTitle2 || `Cowboy \xB7 ${head}`,
11541
+ userSetTitle: Boolean(explicitTitle2),
11542
+ sourceType: "branch",
11543
+ sourceRef: head,
11544
+ branchName: head,
11545
+ worktreePath: repoPath,
11546
+ repoPath,
11547
+ agent: resolved.agent,
11548
+ autonomy: input.autonomy ?? "default",
11549
+ model: resolved.model,
11550
+ effort: resolved.effort,
11551
+ fast: resolved.fast,
11552
+ planMode: Boolean(input.planMode),
11553
+ attachments: input.attachments ?? [],
11554
+ sourceIsFork: false,
11555
+ parentThreadId: input.parentThreadId ?? null,
11556
+ status: "idle",
11557
+ cowboy: true
11558
+ });
11559
+ writeThread(thread2);
11560
+ await ensureWorkspace(repoPath);
11561
+ return readThread(thread2.id) ?? thread2;
11562
+ }
11297
11563
  let sourceRef = input.sourceRef;
11298
11564
  let sourceIsFork = false;
11299
11565
  let prUrl = null;
@@ -11379,6 +11645,24 @@ var init_create = __esm({
11379
11645
  }
11380
11646
  });
11381
11647
 
11648
+ // src/threads/cowboy.ts
11649
+ function isCowboyThread(thread) {
11650
+ return Boolean(thread?.cowboy);
11651
+ }
11652
+ function isPrimaryCheckoutThread(thread) {
11653
+ if (!thread?.worktreePath || !thread.repoPath) return false;
11654
+ return normalizeWorktreePath(thread.worktreePath) === normalizeWorktreePath(thread.repoPath);
11655
+ }
11656
+ function shouldRemoveWorktreeOnTeardown(thread) {
11657
+ return !isCowboyThread(thread) && !isPrimaryCheckoutThread(thread);
11658
+ }
11659
+ var init_cowboy = __esm({
11660
+ "src/threads/cowboy.ts"() {
11661
+ "use strict";
11662
+ init_worktree_labels();
11663
+ }
11664
+ });
11665
+
11382
11666
  // src/store/turn-live.ts
11383
11667
  function trimParts(parts) {
11384
11668
  if (parts.length <= MAX_PARTS) return parts;
@@ -13021,13 +13305,22 @@ async function previewLand(thread) {
13021
13305
  isFork: true
13022
13306
  };
13023
13307
  }
13024
- const target = await resolveDefaultBranch(thread.repoPath);
13025
- if (thread.branchName === target || thread.branchName === "main" || thread.branchName === "master") {
13308
+ if (isCowboyThread(thread)) {
13309
+ const current2 = await currentBranch(thread.worktreePath);
13310
+ const dirty = await isDirty(thread.worktreePath);
13311
+ const diff2 = await getDiff(thread.worktreePath, thread.repoPath, { base: current2 });
13312
+ return {
13313
+ branch: current2,
13314
+ target: current2,
13315
+ diffStat: diff2.stat,
13316
+ dirty,
13317
+ blocked: false,
13318
+ isFork: false,
13319
+ cowboy: true
13320
+ };
13026
13321
  }
13027
- const { stdout: head } = await Promise.resolve().then(() => (init_run(), run_exports)).then(
13028
- ({ git: git2 }) => git2(["rev-parse", "--abbrev-ref", "HEAD"], thread.worktreePath)
13029
- );
13030
- const current = head.trim();
13322
+ const target = await resolveDefaultBranch(thread.repoPath);
13323
+ const current = await currentBranch(thread.worktreePath);
13031
13324
  if (current === target) {
13032
13325
  return {
13033
13326
  branch: current,
@@ -13054,6 +13347,19 @@ async function confirmLand(thread, opts) {
13054
13347
  if (preview.blocked) {
13055
13348
  throw new Error(preview.blockReason ?? "Land blocked");
13056
13349
  }
13350
+ if (preview.cowboy) {
13351
+ const meta2 = await suggestPrMetadata(thread.worktreePath, {
13352
+ base: preview.branch,
13353
+ fallbackTitle: thread.title,
13354
+ sourceLabel: `${thread.sourceType}:${thread.sourceRef}`
13355
+ });
13356
+ let committed2 = false;
13357
+ if (preview.dirty) {
13358
+ committed2 = await commitAll(thread.worktreePath, meta2.commitMessage);
13359
+ }
13360
+ await pushBranch(thread.worktreePath, preview.branch);
13361
+ return { prUrl: null, pushed: true, committed: committed2 };
13362
+ }
13057
13363
  const meta = await suggestPrMetadata(thread.worktreePath, {
13058
13364
  base: preview.target,
13059
13365
  fallbackTitle: thread.title,
@@ -13098,6 +13404,7 @@ var init_land = __esm({
13098
13404
  init_diff();
13099
13405
  init_pr_metadata();
13100
13406
  init_gh_errors();
13407
+ init_cowboy();
13101
13408
  }
13102
13409
  });
13103
13410
 
@@ -13683,7 +13990,7 @@ var init_instructions = __esm({
13683
13990
  });
13684
13991
 
13685
13992
  // src/plan/plan-present.ts
13686
- function asRecord4(v) {
13993
+ function asRecord5(v) {
13687
13994
  return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
13688
13995
  }
13689
13996
  function isPresentPlanToolName(name) {
@@ -13695,7 +14002,7 @@ function extractPresentedPlan(parts) {
13695
14002
  for (let i = parts.length - 1; i >= 0; i--) {
13696
14003
  const p = parts[i];
13697
14004
  if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
13698
- const input = asRecord4(p.input) ?? {};
14005
+ const input = asRecord5(p.input) ?? {};
13699
14006
  const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
13700
14007
  if (!content.trim()) continue;
13701
14008
  const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
@@ -14558,6 +14865,7 @@ var init_orchestrator = __esm({
14558
14865
  init_thread_store();
14559
14866
  init_desktop_host();
14560
14867
  init_create();
14868
+ init_cowboy();
14561
14869
  init_orchestrator_capable();
14562
14870
  init_chat_tabs();
14563
14871
  init_enqueue_by_key();
@@ -14834,7 +15142,9 @@ var init_orchestrator = __esm({
14834
15142
  return thread;
14835
15143
  }
14836
15144
  async finishCreateThread(threadId, prompt) {
14837
- const setup = this.runSetupAfterCreate(threadId);
15145
+ const created = this.getThread(threadId);
15146
+ const skipSetup = isCowboyThread(created);
15147
+ const setup = skipSetup ? Promise.resolve() : this.runSetupAfterCreate(threadId);
14838
15148
  if (prompt) {
14839
15149
  try {
14840
15150
  await this.send(threadId, prompt);
@@ -14846,6 +15156,7 @@ var init_orchestrator = __esm({
14846
15156
  }
14847
15157
  }
14848
15158
  await setup;
15159
+ if (skipSetup) return;
14849
15160
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
14850
15161
  if (autoRunAfterSetupEnabled2()) {
14851
15162
  try {
@@ -16177,17 +16488,19 @@ var init_orchestrator = __esm({
16177
16488
  const siblings = threadsSharingWorktree(thread.worktreePath).filter((t) => t.id !== thread.id);
16178
16489
  if (siblings.length === 0) {
16179
16490
  this.stopDev(thread.id);
16180
- try {
16181
- await runArchiveScript(thread.repoPath, thread.worktreePath, (line) => {
16182
- this.emit({
16183
- type: "turn_output",
16184
- threadId: thread.id,
16185
- event: { type: "stdout", data: `[archive] ${line}` }
16491
+ if (shouldRemoveWorktreeOnTeardown(thread)) {
16492
+ try {
16493
+ await runArchiveScript(thread.repoPath, thread.worktreePath, (line) => {
16494
+ this.emit({
16495
+ type: "turn_output",
16496
+ threadId: thread.id,
16497
+ event: { type: "stdout", data: `[archive] ${line}` }
16498
+ });
16186
16499
  });
16187
- });
16188
- } catch {
16500
+ } catch {
16501
+ }
16502
+ await removeWorktree(thread.repoPath, thread.worktreePath);
16189
16503
  }
16190
- await removeWorktree(thread.repoPath, thread.worktreePath);
16191
16504
  }
16192
16505
  const archived = setStatus(thread.id, "archived");
16193
16506
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
@@ -16215,15 +16528,17 @@ var init_orchestrator = __esm({
16215
16528
  const siblings = threadsSharingWorktree(thread.worktreePath).filter((t) => t.id !== thread.id);
16216
16529
  if (siblings.length === 0) {
16217
16530
  this.stopDev(thread.id);
16218
- try {
16219
- await runArchiveScript(thread.repoPath, thread.worktreePath);
16220
- } catch {
16531
+ if (shouldRemoveWorktreeOnTeardown(thread)) {
16532
+ try {
16533
+ await runArchiveScript(thread.repoPath, thread.worktreePath);
16534
+ } catch {
16535
+ }
16536
+ const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
16537
+ const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
16538
+ await removeWorktree(thread.repoPath, thread.worktreePath, {
16539
+ deleteBranch: deleteBranch ? thread.branchName : void 0
16540
+ });
16221
16541
  }
16222
- const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
16223
- const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
16224
- await removeWorktree(thread.repoPath, thread.worktreePath, {
16225
- deleteBranch: deleteBranch ? thread.branchName : void 0
16226
- });
16227
16542
  }
16228
16543
  deleteThreadRecord(thread.id);
16229
16544
  }
@@ -16240,6 +16555,11 @@ var init_orchestrator = __esm({
16240
16555
  return restored2;
16241
16556
  }
16242
16557
  if (!(0, import_node_fs44.existsSync)(thread.worktreePath)) {
16558
+ if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
16559
+ throw new Error(
16560
+ `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
16561
+ );
16562
+ }
16243
16563
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
16244
16564
  const slug = thread.worktreePath.split("/").pop();
16245
16565
  const dest = thread.worktreePath;
@@ -17886,7 +18206,7 @@ async function startMcpServer() {
17886
18206
  );
17887
18207
  server.tool(
17888
18208
  "create_thread",
17889
- `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn. Then use send_to_thread to chat.`,
18209
+ `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
17890
18210
  {
17891
18211
  sourceType: import_zod4.z.enum(["branch", "pr", "ticket"]),
17892
18212
  sourceRef: import_zod4.z.string(),
@@ -17896,6 +18216,9 @@ async function startMcpServer() {
17896
18216
  ),
17897
18217
  repoPath: import_zod4.z.string(),
17898
18218
  title: import_zod4.z.string().optional(),
18219
+ cowboy: import_zod4.z.boolean().optional().describe(
18220
+ "If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
18221
+ ),
17899
18222
  parentThreadId: import_zod4.z.string().optional().describe(
17900
18223
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
17901
18224
  )
@@ -17956,7 +18279,8 @@ async function startMcpServer() {
17956
18279
  fast: opts.fast,
17957
18280
  repoPath: args.repoPath,
17958
18281
  title: args.title,
17959
- parentThreadId: parentId || null
18282
+ parentThreadId: parentId || null,
18283
+ cowboy: args.cowboy || void 0
17960
18284
  }),
17961
18285
  CREATE_THREAD_TIMEOUT_MS,
17962
18286
  "create_thread"
@@ -17974,6 +18298,7 @@ async function startMcpServer() {
17974
18298
  agent: thread.agent,
17975
18299
  model: thread.model,
17976
18300
  status: thread.status,
18301
+ cowboy: Boolean(thread.cowboy),
17977
18302
  link: `sideboard://thread/${thread.id}`,
17978
18303
  parentThreadId: thread.parentThreadId,
17979
18304
  ...agentCoercedFrom ? {