webmux 0.18.0 → 0.20.0

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.
@@ -2,7 +2,7 @@
2
2
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
3
3
  var __require = import.meta.require;
4
4
 
5
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js
5
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
6
6
  var require_identity = __commonJS((exports) => {
7
7
  var ALIAS = Symbol.for("yaml.alias");
8
8
  var DOC = Symbol.for("yaml.document");
@@ -56,7 +56,7 @@ var require_identity = __commonJS((exports) => {
56
56
  exports.isSeq = isSeq;
57
57
  });
58
58
 
59
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/visit.js
59
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/visit.js
60
60
  var require_visit = __commonJS((exports) => {
61
61
  var identity = require_identity();
62
62
  var BREAK = Symbol("break visit");
@@ -211,7 +211,7 @@ var require_visit = __commonJS((exports) => {
211
211
  exports.visitAsync = visitAsync;
212
212
  });
213
213
 
214
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/doc/directives.js
214
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/directives.js
215
215
  var require_directives = __commonJS((exports) => {
216
216
  var identity = require_identity();
217
217
  var visit = require_visit();
@@ -363,7 +363,7 @@ var require_directives = __commonJS((exports) => {
363
363
  exports.Directives = Directives;
364
364
  });
365
365
 
366
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/doc/anchors.js
366
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/anchors.js
367
367
  var require_anchors = __commonJS((exports) => {
368
368
  var identity = require_identity();
369
369
  var visit = require_visit();
@@ -425,7 +425,7 @@ var require_anchors = __commonJS((exports) => {
425
425
  exports.findNewAnchor = findNewAnchor;
426
426
  });
427
427
 
428
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/doc/applyReviver.js
428
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/applyReviver.js
429
429
  var require_applyReviver = __commonJS((exports) => {
430
430
  function applyReviver(reviver, obj, key, val) {
431
431
  if (val && typeof val === "object") {
@@ -472,7 +472,7 @@ var require_applyReviver = __commonJS((exports) => {
472
472
  exports.applyReviver = applyReviver;
473
473
  });
474
474
 
475
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/toJS.js
475
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/toJS.js
476
476
  var require_toJS = __commonJS((exports) => {
477
477
  var identity = require_identity();
478
478
  function toJS(value, arg, ctx) {
@@ -499,7 +499,7 @@ var require_toJS = __commonJS((exports) => {
499
499
  exports.toJS = toJS;
500
500
  });
501
501
 
502
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/Node.js
502
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Node.js
503
503
  var require_Node = __commonJS((exports) => {
504
504
  var applyReviver = require_applyReviver();
505
505
  var identity = require_identity();
@@ -536,7 +536,7 @@ var require_Node = __commonJS((exports) => {
536
536
  exports.NodeBase = NodeBase;
537
537
  });
538
538
 
539
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/Alias.js
539
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Alias.js
540
540
  var require_Alias = __commonJS((exports) => {
541
541
  var anchors = require_anchors();
542
542
  var visit = require_visit();
@@ -644,7 +644,7 @@ var require_Alias = __commonJS((exports) => {
644
644
  exports.Alias = Alias;
645
645
  });
646
646
 
647
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/Scalar.js
647
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Scalar.js
648
648
  var require_Scalar = __commonJS((exports) => {
649
649
  var identity = require_identity();
650
650
  var Node = require_Node();
@@ -672,7 +672,7 @@ var require_Scalar = __commonJS((exports) => {
672
672
  exports.isScalarValue = isScalarValue;
673
673
  });
674
674
 
675
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/doc/createNode.js
675
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/createNode.js
676
676
  var require_createNode = __commonJS((exports) => {
677
677
  var Alias = require_Alias();
678
678
  var identity = require_identity();
@@ -744,7 +744,7 @@ var require_createNode = __commonJS((exports) => {
744
744
  exports.createNode = createNode;
745
745
  });
746
746
 
747
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/Collection.js
747
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Collection.js
748
748
  var require_Collection = __commonJS((exports) => {
749
749
  var createNode = require_createNode();
750
750
  var identity = require_identity();
@@ -859,7 +859,7 @@ var require_Collection = __commonJS((exports) => {
859
859
  exports.isEmptyPath = isEmptyPath;
860
860
  });
861
861
 
862
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyComment.js
862
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyComment.js
863
863
  var require_stringifyComment = __commonJS((exports) => {
864
864
  var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
865
865
  function indentComment(comment, indent) {
@@ -876,7 +876,7 @@ var require_stringifyComment = __commonJS((exports) => {
876
876
  exports.stringifyComment = stringifyComment;
877
877
  });
878
878
 
879
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/foldFlowLines.js
879
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/foldFlowLines.js
880
880
  var require_foldFlowLines = __commonJS((exports) => {
881
881
  var FOLD_FLOW = "flow";
882
882
  var FOLD_BLOCK = "block";
@@ -1013,7 +1013,7 @@ ${indent}${text.slice(fold + 1, end2)}`;
1013
1013
  exports.foldFlowLines = foldFlowLines;
1014
1014
  });
1015
1015
 
1016
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyString.js
1016
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyString.js
1017
1017
  var require_stringifyString = __commonJS((exports) => {
1018
1018
  var Scalar = require_Scalar();
1019
1019
  var foldFlowLines = require_foldFlowLines();
@@ -1311,7 +1311,7 @@ ${indent}`);
1311
1311
  exports.stringifyString = stringifyString;
1312
1312
  });
1313
1313
 
1314
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringify.js
1314
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringify.js
1315
1315
  var require_stringify = __commonJS((exports) => {
1316
1316
  var anchors = require_anchors();
1317
1317
  var identity = require_identity();
@@ -1334,6 +1334,7 @@ var require_stringify = __commonJS((exports) => {
1334
1334
  nullStr: "null",
1335
1335
  simpleKeys: false,
1336
1336
  singleQuote: null,
1337
+ trailingComma: false,
1337
1338
  trueStr: "true",
1338
1339
  verifyAliasOrder: true
1339
1340
  }, doc.schema.toStringOptions, options);
@@ -1431,7 +1432,7 @@ ${ctx.indent}${str}`;
1431
1432
  exports.stringify = stringify;
1432
1433
  });
1433
1434
 
1434
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyPair.js
1435
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyPair.js
1435
1436
  var require_stringifyPair = __commonJS((exports) => {
1436
1437
  var identity = require_identity();
1437
1438
  var Scalar = require_Scalar();
@@ -1567,7 +1568,7 @@ ${ctx.indent}`;
1567
1568
  exports.stringifyPair = stringifyPair;
1568
1569
  });
1569
1570
 
1570
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/log.js
1571
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/log.js
1571
1572
  var require_log = __commonJS((exports) => {
1572
1573
  var node_process = __require("process");
1573
1574
  function debug(logLevel, ...messages) {
@@ -1586,7 +1587,7 @@ var require_log = __commonJS((exports) => {
1586
1587
  exports.warn = warn;
1587
1588
  });
1588
1589
 
1589
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/merge.js
1590
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/merge.js
1590
1591
  var require_merge = __commonJS((exports) => {
1591
1592
  var identity = require_identity();
1592
1593
  var Scalar = require_Scalar();
@@ -1640,7 +1641,7 @@ var require_merge = __commonJS((exports) => {
1640
1641
  exports.merge = merge;
1641
1642
  });
1642
1643
 
1643
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/addPairToJSMap.js
1644
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js
1644
1645
  var require_addPairToJSMap = __commonJS((exports) => {
1645
1646
  var log2 = require_log();
1646
1647
  var merge = require_merge();
@@ -1701,7 +1702,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
1701
1702
  exports.addPairToJSMap = addPairToJSMap;
1702
1703
  });
1703
1704
 
1704
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/Pair.js
1705
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Pair.js
1705
1706
  var require_Pair = __commonJS((exports) => {
1706
1707
  var createNode = require_createNode();
1707
1708
  var stringifyPair = require_stringifyPair();
@@ -1739,7 +1740,7 @@ var require_Pair = __commonJS((exports) => {
1739
1740
  exports.createPair = createPair;
1740
1741
  });
1741
1742
 
1742
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyCollection.js
1743
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyCollection.js
1743
1744
  var require_stringifyCollection = __commonJS((exports) => {
1744
1745
  var identity = require_identity();
1745
1746
  var stringify = require_stringify();
@@ -1842,13 +1843,20 @@ ${indent}${line}` : `
1842
1843
  if (comment)
1843
1844
  reqNewline = true;
1844
1845
  let str = stringify.stringify(item, itemCtx, () => comment = null);
1845
- if (i < items.length - 1)
1846
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes(`
1847
+ `));
1848
+ if (i < items.length - 1) {
1846
1849
  str += ",";
1850
+ } else if (ctx.options.trailingComma) {
1851
+ if (ctx.options.lineWidth > 0) {
1852
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
1853
+ }
1854
+ if (reqNewline) {
1855
+ str += ",";
1856
+ }
1857
+ }
1847
1858
  if (comment)
1848
1859
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
1849
- if (!reqNewline && (lines.length > linesAtValue || str.includes(`
1850
- `)))
1851
- reqNewline = true;
1852
1860
  lines.push(str);
1853
1861
  linesAtValue = lines.length;
1854
1862
  }
@@ -1884,7 +1892,7 @@ ${indent}${end}`;
1884
1892
  exports.stringifyCollection = stringifyCollection;
1885
1893
  });
1886
1894
 
1887
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLMap.js
1895
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLMap.js
1888
1896
  var require_YAMLMap = __commonJS((exports) => {
1889
1897
  var stringifyCollection = require_stringifyCollection();
1890
1898
  var addPairToJSMap = require_addPairToJSMap();
@@ -2011,7 +2019,7 @@ var require_YAMLMap = __commonJS((exports) => {
2011
2019
  exports.findPair = findPair;
2012
2020
  });
2013
2021
 
2014
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/common/map.js
2022
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/map.js
2015
2023
  var require_map = __commonJS((exports) => {
2016
2024
  var identity = require_identity();
2017
2025
  var YAMLMap = require_YAMLMap();
@@ -2030,7 +2038,7 @@ var require_map = __commonJS((exports) => {
2030
2038
  exports.map = map;
2031
2039
  });
2032
2040
 
2033
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/nodes/YAMLSeq.js
2041
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLSeq.js
2034
2042
  var require_YAMLSeq = __commonJS((exports) => {
2035
2043
  var createNode = require_createNode();
2036
2044
  var stringifyCollection = require_stringifyCollection();
@@ -2123,7 +2131,7 @@ var require_YAMLSeq = __commonJS((exports) => {
2123
2131
  exports.YAMLSeq = YAMLSeq;
2124
2132
  });
2125
2133
 
2126
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/common/seq.js
2134
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/seq.js
2127
2135
  var require_seq = __commonJS((exports) => {
2128
2136
  var identity = require_identity();
2129
2137
  var YAMLSeq = require_YAMLSeq();
@@ -2142,7 +2150,7 @@ var require_seq = __commonJS((exports) => {
2142
2150
  exports.seq = seq;
2143
2151
  });
2144
2152
 
2145
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/common/string.js
2153
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/string.js
2146
2154
  var require_string = __commonJS((exports) => {
2147
2155
  var stringifyString = require_stringifyString();
2148
2156
  var string = {
@@ -2158,7 +2166,7 @@ var require_string = __commonJS((exports) => {
2158
2166
  exports.string = string;
2159
2167
  });
2160
2168
 
2161
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/common/null.js
2169
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/null.js
2162
2170
  var require_null = __commonJS((exports) => {
2163
2171
  var Scalar = require_Scalar();
2164
2172
  var nullTag = {
@@ -2173,7 +2181,7 @@ var require_null = __commonJS((exports) => {
2173
2181
  exports.nullTag = nullTag;
2174
2182
  });
2175
2183
 
2176
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/core/bool.js
2184
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/bool.js
2177
2185
  var require_bool = __commonJS((exports) => {
2178
2186
  var Scalar = require_Scalar();
2179
2187
  var boolTag = {
@@ -2194,7 +2202,7 @@ var require_bool = __commonJS((exports) => {
2194
2202
  exports.boolTag = boolTag;
2195
2203
  });
2196
2204
 
2197
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyNumber.js
2205
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyNumber.js
2198
2206
  var require_stringifyNumber = __commonJS((exports) => {
2199
2207
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
2200
2208
  if (typeof value === "bigint")
@@ -2218,7 +2226,7 @@ var require_stringifyNumber = __commonJS((exports) => {
2218
2226
  exports.stringifyNumber = stringifyNumber;
2219
2227
  });
2220
2228
 
2221
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/core/float.js
2229
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/float.js
2222
2230
  var require_float = __commonJS((exports) => {
2223
2231
  var Scalar = require_Scalar();
2224
2232
  var stringifyNumber = require_stringifyNumber();
@@ -2261,7 +2269,7 @@ var require_float = __commonJS((exports) => {
2261
2269
  exports.floatNaN = floatNaN;
2262
2270
  });
2263
2271
 
2264
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/core/int.js
2272
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/int.js
2265
2273
  var require_int = __commonJS((exports) => {
2266
2274
  var stringifyNumber = require_stringifyNumber();
2267
2275
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2303,7 +2311,7 @@ var require_int = __commonJS((exports) => {
2303
2311
  exports.intOct = intOct;
2304
2312
  });
2305
2313
 
2306
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/core/schema.js
2314
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/schema.js
2307
2315
  var require_schema = __commonJS((exports) => {
2308
2316
  var map = require_map();
2309
2317
  var _null = require_null();
@@ -2328,7 +2336,7 @@ var require_schema = __commonJS((exports) => {
2328
2336
  exports.schema = schema;
2329
2337
  });
2330
2338
 
2331
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/json/schema.js
2339
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/json/schema.js
2332
2340
  var require_schema2 = __commonJS((exports) => {
2333
2341
  var Scalar = require_Scalar();
2334
2342
  var map = require_map();
@@ -2392,7 +2400,7 @@ var require_schema2 = __commonJS((exports) => {
2392
2400
  exports.schema = schema;
2393
2401
  });
2394
2402
 
2395
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/binary.js
2403
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/binary.js
2396
2404
  var require_binary = __commonJS((exports) => {
2397
2405
  var node_buffer = __require("buffer");
2398
2406
  var Scalar = require_Scalar();
@@ -2447,7 +2455,7 @@ var require_binary = __commonJS((exports) => {
2447
2455
  exports.binary = binary;
2448
2456
  });
2449
2457
 
2450
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2458
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2451
2459
  var require_pairs = __commonJS((exports) => {
2452
2460
  var identity = require_identity();
2453
2461
  var Pair = require_Pair();
@@ -2522,7 +2530,7 @@ ${cn.comment}` : item.comment;
2522
2530
  exports.resolvePairs = resolvePairs;
2523
2531
  });
2524
2532
 
2525
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/omap.js
2533
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/omap.js
2526
2534
  var require_omap = __commonJS((exports) => {
2527
2535
  var identity = require_identity();
2528
2536
  var toJS = require_toJS();
@@ -2594,7 +2602,7 @@ var require_omap = __commonJS((exports) => {
2594
2602
  exports.omap = omap;
2595
2603
  });
2596
2604
 
2597
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/bool.js
2605
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/bool.js
2598
2606
  var require_bool2 = __commonJS((exports) => {
2599
2607
  var Scalar = require_Scalar();
2600
2608
  function boolStringify({ value, source }, ctx) {
@@ -2623,7 +2631,7 @@ var require_bool2 = __commonJS((exports) => {
2623
2631
  exports.trueTag = trueTag;
2624
2632
  });
2625
2633
 
2626
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/float.js
2634
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/float.js
2627
2635
  var require_float2 = __commonJS((exports) => {
2628
2636
  var Scalar = require_Scalar();
2629
2637
  var stringifyNumber = require_stringifyNumber();
@@ -2669,7 +2677,7 @@ var require_float2 = __commonJS((exports) => {
2669
2677
  exports.floatNaN = floatNaN;
2670
2678
  });
2671
2679
 
2672
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/int.js
2680
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/int.js
2673
2681
  var require_int2 = __commonJS((exports) => {
2674
2682
  var stringifyNumber = require_stringifyNumber();
2675
2683
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2745,7 +2753,7 @@ var require_int2 = __commonJS((exports) => {
2745
2753
  exports.intOct = intOct;
2746
2754
  });
2747
2755
 
2748
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/set.js
2756
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/set.js
2749
2757
  var require_set = __commonJS((exports) => {
2750
2758
  var identity = require_identity();
2751
2759
  var Pair = require_Pair();
@@ -2828,7 +2836,7 @@ var require_set = __commonJS((exports) => {
2828
2836
  exports.set = set;
2829
2837
  });
2830
2838
 
2831
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2839
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2832
2840
  var require_timestamp = __commonJS((exports) => {
2833
2841
  var stringifyNumber = require_stringifyNumber();
2834
2842
  function parseSexagesimal(str, asBigInt) {
@@ -2910,7 +2918,7 @@ var require_timestamp = __commonJS((exports) => {
2910
2918
  exports.timestamp = timestamp;
2911
2919
  });
2912
2920
 
2913
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/yaml-1.1/schema.js
2921
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/schema.js
2914
2922
  var require_schema3 = __commonJS((exports) => {
2915
2923
  var map = require_map();
2916
2924
  var _null = require_null();
@@ -2951,7 +2959,7 @@ var require_schema3 = __commonJS((exports) => {
2951
2959
  exports.schema = schema;
2952
2960
  });
2953
2961
 
2954
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/tags.js
2962
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/tags.js
2955
2963
  var require_tags = __commonJS((exports) => {
2956
2964
  var map = require_map();
2957
2965
  var _null = require_null();
@@ -3042,7 +3050,7 @@ var require_tags = __commonJS((exports) => {
3042
3050
  exports.getTags = getTags;
3043
3051
  });
3044
3052
 
3045
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/schema/Schema.js
3053
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/Schema.js
3046
3054
  var require_Schema = __commonJS((exports) => {
3047
3055
  var identity = require_identity();
3048
3056
  var map = require_map();
@@ -3072,7 +3080,7 @@ var require_Schema = __commonJS((exports) => {
3072
3080
  exports.Schema = Schema;
3073
3081
  });
3074
3082
 
3075
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/stringify/stringifyDocument.js
3083
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyDocument.js
3076
3084
  var require_stringifyDocument = __commonJS((exports) => {
3077
3085
  var identity = require_identity();
3078
3086
  var stringify = require_stringify();
@@ -3152,7 +3160,7 @@ var require_stringifyDocument = __commonJS((exports) => {
3152
3160
  exports.stringifyDocument = stringifyDocument;
3153
3161
  });
3154
3162
 
3155
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/doc/Document.js
3163
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/Document.js
3156
3164
  var require_Document = __commonJS((exports) => {
3157
3165
  var Alias = require_Alias();
3158
3166
  var Collection = require_Collection();
@@ -3387,7 +3395,7 @@ var require_Document = __commonJS((exports) => {
3387
3395
  exports.Document = Document;
3388
3396
  });
3389
3397
 
3390
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/errors.js
3398
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/errors.js
3391
3399
  var require_errors = __commonJS((exports) => {
3392
3400
  class YAMLError extends Error {
3393
3401
  constructor(name, pos, code, message) {
@@ -3452,7 +3460,7 @@ ${pointer}
3452
3460
  exports.prettifyError = prettifyError;
3453
3461
  });
3454
3462
 
3455
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-props.js
3463
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-props.js
3456
3464
  var require_resolve_props = __commonJS((exports) => {
3457
3465
  function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3458
3466
  let spaceBefore = false;
@@ -3582,7 +3590,7 @@ var require_resolve_props = __commonJS((exports) => {
3582
3590
  exports.resolveProps = resolveProps;
3583
3591
  });
3584
3592
 
3585
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/util-contains-newline.js
3593
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-contains-newline.js
3586
3594
  var require_util_contains_newline = __commonJS((exports) => {
3587
3595
  function containsNewline(key) {
3588
3596
  if (!key)
@@ -3622,7 +3630,7 @@ var require_util_contains_newline = __commonJS((exports) => {
3622
3630
  exports.containsNewline = containsNewline;
3623
3631
  });
3624
3632
 
3625
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/util-flow-indent-check.js
3633
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-flow-indent-check.js
3626
3634
  var require_util_flow_indent_check = __commonJS((exports) => {
3627
3635
  var utilContainsNewline = require_util_contains_newline();
3628
3636
  function flowIndentCheck(indent, fc, onError) {
@@ -3637,7 +3645,7 @@ var require_util_flow_indent_check = __commonJS((exports) => {
3637
3645
  exports.flowIndentCheck = flowIndentCheck;
3638
3646
  });
3639
3647
 
3640
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/util-map-includes.js
3648
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-map-includes.js
3641
3649
  var require_util_map_includes = __commonJS((exports) => {
3642
3650
  var identity = require_identity();
3643
3651
  function mapIncludes(ctx, items, search) {
@@ -3650,7 +3658,7 @@ var require_util_map_includes = __commonJS((exports) => {
3650
3658
  exports.mapIncludes = mapIncludes;
3651
3659
  });
3652
3660
 
3653
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-map.js
3661
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-map.js
3654
3662
  var require_resolve_block_map = __commonJS((exports) => {
3655
3663
  var Pair = require_Pair();
3656
3664
  var YAMLMap = require_YAMLMap();
@@ -3757,7 +3765,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3757
3765
  exports.resolveBlockMap = resolveBlockMap;
3758
3766
  });
3759
3767
 
3760
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-seq.js
3768
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-seq.js
3761
3769
  var require_resolve_block_seq = __commonJS((exports) => {
3762
3770
  var YAMLSeq = require_YAMLSeq();
3763
3771
  var resolveProps = require_resolve_props();
@@ -3805,7 +3813,7 @@ var require_resolve_block_seq = __commonJS((exports) => {
3805
3813
  exports.resolveBlockSeq = resolveBlockSeq;
3806
3814
  });
3807
3815
 
3808
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-end.js
3816
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-end.js
3809
3817
  var require_resolve_end = __commonJS((exports) => {
3810
3818
  function resolveEnd(end, offset, reqSpace, onError) {
3811
3819
  let comment = "";
@@ -3845,7 +3853,7 @@ var require_resolve_end = __commonJS((exports) => {
3845
3853
  exports.resolveEnd = resolveEnd;
3846
3854
  });
3847
3855
 
3848
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-collection.js
3856
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-collection.js
3849
3857
  var require_resolve_flow_collection = __commonJS((exports) => {
3850
3858
  var identity = require_identity();
3851
3859
  var Pair = require_Pair();
@@ -4036,7 +4044,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
4036
4044
  exports.resolveFlowCollection = resolveFlowCollection;
4037
4045
  });
4038
4046
 
4039
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/compose-collection.js
4047
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-collection.js
4040
4048
  var require_compose_collection = __commonJS((exports) => {
4041
4049
  var identity = require_identity();
4042
4050
  var Scalar = require_Scalar();
@@ -4098,7 +4106,7 @@ var require_compose_collection = __commonJS((exports) => {
4098
4106
  exports.composeCollection = composeCollection;
4099
4107
  });
4100
4108
 
4101
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-block-scalar.js
4109
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-scalar.js
4102
4110
  var require_resolve_block_scalar = __commonJS((exports) => {
4103
4111
  var Scalar = require_Scalar();
4104
4112
  function resolveBlockScalar(ctx, scalar, onError) {
@@ -4291,7 +4299,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
4291
4299
  exports.resolveBlockScalar = resolveBlockScalar;
4292
4300
  });
4293
4301
 
4294
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/resolve-flow-scalar.js
4302
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-scalar.js
4295
4303
  var require_resolve_flow_scalar = __commonJS((exports) => {
4296
4304
  var Scalar = require_Scalar();
4297
4305
  var resolveEnd = require_resolve_end();
@@ -4507,7 +4515,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
4507
4515
  exports.resolveFlowScalar = resolveFlowScalar;
4508
4516
  });
4509
4517
 
4510
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/compose-scalar.js
4518
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-scalar.js
4511
4519
  var require_compose_scalar = __commonJS((exports) => {
4512
4520
  var identity = require_identity();
4513
4521
  var Scalar = require_Scalar();
@@ -4585,7 +4593,7 @@ var require_compose_scalar = __commonJS((exports) => {
4585
4593
  exports.composeScalar = composeScalar;
4586
4594
  });
4587
4595
 
4588
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/util-empty-scalar-position.js
4596
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-empty-scalar-position.js
4589
4597
  var require_util_empty_scalar_position = __commonJS((exports) => {
4590
4598
  function emptyScalarPosition(offset, before, pos) {
4591
4599
  if (before) {
@@ -4612,7 +4620,7 @@ var require_util_empty_scalar_position = __commonJS((exports) => {
4612
4620
  exports.emptyScalarPosition = emptyScalarPosition;
4613
4621
  });
4614
4622
 
4615
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/compose-node.js
4623
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-node.js
4616
4624
  var require_compose_node = __commonJS((exports) => {
4617
4625
  var Alias = require_Alias();
4618
4626
  var identity = require_identity();
@@ -4643,17 +4651,22 @@ var require_compose_node = __commonJS((exports) => {
4643
4651
  case "block-map":
4644
4652
  case "block-seq":
4645
4653
  case "flow-collection":
4646
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
4647
- if (anchor)
4648
- node.anchor = anchor.source.substring(1);
4654
+ try {
4655
+ node = composeCollection.composeCollection(CN, ctx, token, props, onError);
4656
+ if (anchor)
4657
+ node.anchor = anchor.source.substring(1);
4658
+ } catch (error) {
4659
+ const message = error instanceof Error ? error.message : String(error);
4660
+ onError(token, "RESOURCE_EXHAUSTION", message);
4661
+ }
4649
4662
  break;
4650
4663
  default: {
4651
4664
  const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
4652
4665
  onError(token, "UNEXPECTED_TOKEN", message);
4653
- node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
4654
4666
  isSrcToken = false;
4655
4667
  }
4656
4668
  }
4669
+ node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
4657
4670
  if (anchor && node.anchor === "")
4658
4671
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
4659
4672
  if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
@@ -4710,7 +4723,7 @@ var require_compose_node = __commonJS((exports) => {
4710
4723
  exports.composeNode = composeNode;
4711
4724
  });
4712
4725
 
4713
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/compose-doc.js
4726
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-doc.js
4714
4727
  var require_compose_doc = __commonJS((exports) => {
4715
4728
  var Document = require_Document();
4716
4729
  var composeNode = require_compose_node();
@@ -4750,7 +4763,7 @@ var require_compose_doc = __commonJS((exports) => {
4750
4763
  exports.composeDoc = composeDoc;
4751
4764
  });
4752
4765
 
4753
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/compose/composer.js
4766
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/composer.js
4754
4767
  var require_composer = __commonJS((exports) => {
4755
4768
  var node_process = __require("process");
4756
4769
  var directives = require_directives();
@@ -4939,7 +4952,7 @@ ${end.comment}` : end.comment;
4939
4952
  exports.Composer = Composer;
4940
4953
  });
4941
4954
 
4942
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/cst-scalar.js
4955
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-scalar.js
4943
4956
  var require_cst_scalar = __commonJS((exports) => {
4944
4957
  var resolveBlockScalar = require_resolve_block_scalar();
4945
4958
  var resolveFlowScalar = require_resolve_flow_scalar();
@@ -5129,7 +5142,7 @@ var require_cst_scalar = __commonJS((exports) => {
5129
5142
  exports.setScalarValue = setScalarValue;
5130
5143
  });
5131
5144
 
5132
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/cst-stringify.js
5145
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-stringify.js
5133
5146
  var require_cst_stringify = __commonJS((exports) => {
5134
5147
  var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
5135
5148
  function stringifyToken(token) {
@@ -5187,7 +5200,7 @@ var require_cst_stringify = __commonJS((exports) => {
5187
5200
  exports.stringify = stringify;
5188
5201
  });
5189
5202
 
5190
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/cst-visit.js
5203
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-visit.js
5191
5204
  var require_cst_visit = __commonJS((exports) => {
5192
5205
  var BREAK = Symbol("break visit");
5193
5206
  var SKIP = Symbol("skip children");
@@ -5246,7 +5259,7 @@ var require_cst_visit = __commonJS((exports) => {
5246
5259
  exports.visit = visit;
5247
5260
  });
5248
5261
 
5249
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/cst.js
5262
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst.js
5250
5263
  var require_cst = __commonJS((exports) => {
5251
5264
  var cstScalar = require_cst_scalar();
5252
5265
  var cstStringify = require_cst_stringify();
@@ -5347,7 +5360,7 @@ var require_cst = __commonJS((exports) => {
5347
5360
  exports.tokenType = tokenType;
5348
5361
  });
5349
5362
 
5350
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/lexer.js
5363
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/lexer.js
5351
5364
  var require_lexer = __commonJS((exports) => {
5352
5365
  var cst = require_cst();
5353
5366
  function isEmpty(ch) {
@@ -5933,7 +5946,7 @@ var require_lexer = __commonJS((exports) => {
5933
5946
  exports.Lexer = Lexer;
5934
5947
  });
5935
5948
 
5936
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/line-counter.js
5949
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/line-counter.js
5937
5950
  var require_line_counter = __commonJS((exports) => {
5938
5951
  class LineCounter {
5939
5952
  constructor() {
@@ -5961,7 +5974,7 @@ var require_line_counter = __commonJS((exports) => {
5961
5974
  exports.LineCounter = LineCounter;
5962
5975
  });
5963
5976
 
5964
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/parse/parser.js
5977
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/parser.js
5965
5978
  var require_parser = __commonJS((exports) => {
5966
5979
  var node_process = __require("process");
5967
5980
  var cst = require_cst();
@@ -6810,7 +6823,7 @@ var require_parser = __commonJS((exports) => {
6810
6823
  exports.Parser = Parser;
6811
6824
  });
6812
6825
 
6813
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/public-api.js
6826
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/public-api.js
6814
6827
  var require_public_api = __commonJS((exports) => {
6815
6828
  var composer = require_composer();
6816
6829
  var Document = require_Document();
@@ -6905,7 +6918,8 @@ var require_public_api = __commonJS((exports) => {
6905
6918
  });
6906
6919
 
6907
6920
  // backend/src/server.ts
6908
- import { join as join6, resolve as resolve6 } from "path";
6921
+ import { randomUUID as randomUUID3 } from "crypto";
6922
+ import { join as join6, resolve as resolve7 } from "path";
6909
6923
  import { mkdirSync } from "fs";
6910
6924
  import { networkInterfaces } from "os";
6911
6925
 
@@ -6933,6 +6947,27 @@ var log = {
6933
6947
  // backend/src/adapters/terminal.ts
6934
6948
  var textDecoder = new TextDecoder;
6935
6949
  var textEncoder = new TextEncoder;
6950
+ function detectPtyWrapper() {
6951
+ if (process.platform === "darwin")
6952
+ return "python3";
6953
+ const scriptResult = Bun.spawnSync(["which", "script"], { stdout: "ignore", stderr: "ignore" });
6954
+ if (scriptResult.exitCode === 0)
6955
+ return "script";
6956
+ const py3Result = Bun.spawnSync(["which", "python3"], { stdout: "ignore", stderr: "ignore" });
6957
+ if (py3Result.exitCode === 0)
6958
+ return "python3";
6959
+ return null;
6960
+ }
6961
+ var ptyWrapper = detectPtyWrapper();
6962
+ function buildPtyArgs(cmd) {
6963
+ if (ptyWrapper === "python3") {
6964
+ return ["python3", "-c", "import pty,sys;pty.spawn(sys.argv[1:])", "bash", "-c", cmd];
6965
+ }
6966
+ if (ptyWrapper === "script") {
6967
+ return ["script", "-q", "-c", cmd, "/dev/null"];
6968
+ }
6969
+ throw new Error("No PTY wrapper found. Install util-linux (provides 'script') or python3, then restart the server.");
6970
+ }
6936
6971
  var DASH_PORT = Bun.env.PORT || "5111";
6937
6972
  var SESSION_PREFIX = `wm-dash-${DASH_PORT}-`;
6938
6973
  var MAX_SCROLLBACK_BYTES = 1 * 1024 * 1024;
@@ -6997,13 +7032,13 @@ function killTmuxSession(name) {
6997
7032
  }
6998
7033
  }
6999
7034
  }
7000
- async function attach(worktreeId, target, cols, rows, initialPane) {
7001
- log.debug(`[term] attach(${worktreeId}) cols=${cols} rows=${rows} existing=${sessions.has(worktreeId)}`);
7002
- if (sessions.has(worktreeId)) {
7003
- await detach(worktreeId);
7035
+ async function attach(attachId, target, cols, rows, initialPane) {
7036
+ log.debug(`[term] attach(${attachId}) cols=${cols} rows=${rows} existing=${sessions.has(attachId)}`);
7037
+ if (sessions.has(attachId)) {
7038
+ await detach(attachId);
7004
7039
  }
7005
7040
  const gName = groupedName();
7006
- log.debug(`[term] attach(${worktreeId}) ownerSession=${target.ownerSessionName} gName=${gName} window=${target.windowName}`);
7041
+ log.debug(`[term] attach(${attachId}) ownerSession=${target.ownerSessionName} gName=${gName} window=${target.windowName}`);
7007
7042
  killTmuxSession(gName);
7008
7043
  const cmd = buildAttachCmd({
7009
7044
  gName,
@@ -7013,8 +7048,7 @@ async function attach(worktreeId, target, cols, rows, initialPane) {
7013
7048
  rows,
7014
7049
  initialPane
7015
7050
  });
7016
- const scriptArgs = process.platform === "darwin" ? ["python3", "-c", "import pty,sys;pty.spawn(sys.argv[1:])", "bash", "-c", cmd] : ["script", "-q", "-c", cmd, "/dev/null"];
7017
- const proc = Bun.spawn(scriptArgs, {
7051
+ const proc = Bun.spawn(buildPtyArgs(cmd), {
7018
7052
  stdin: "pipe",
7019
7053
  stdout: "pipe",
7020
7054
  stderr: "pipe",
@@ -7030,8 +7064,8 @@ async function attach(worktreeId, target, cols, rows, initialPane) {
7030
7064
  onExit: null,
7031
7065
  cancelled: false
7032
7066
  };
7033
- sessions.set(worktreeId, session);
7034
- log.debug(`[term] attach(${worktreeId}) spawned pid=${proc.pid}`);
7067
+ sessions.set(attachId, session);
7068
+ log.debug(`[term] attach(${attachId}) spawned pid=${proc.pid}`);
7035
7069
  (async () => {
7036
7070
  const reader = proc.stdout.getReader();
7037
7071
  try {
@@ -7052,7 +7086,7 @@ async function attach(worktreeId, target, cols, rows, initialPane) {
7052
7086
  }
7053
7087
  } catch (err) {
7054
7088
  if (!session.cancelled) {
7055
- log.error(`[term] stdout reader error(${worktreeId})`, err);
7089
+ log.error(`[term] stdout reader error(${attachId})`, err);
7056
7090
  }
7057
7091
  }
7058
7092
  })();
@@ -7063,55 +7097,55 @@ async function attach(worktreeId, target, cols, rows, initialPane) {
7063
7097
  const { done, value } = await reader.read();
7064
7098
  if (done)
7065
7099
  break;
7066
- log.debug(`[term] stderr(${worktreeId}): ${textDecoder.decode(value).trimEnd()}`);
7100
+ log.debug(`[term] stderr(${attachId}): ${textDecoder.decode(value).trimEnd()}`);
7067
7101
  }
7068
7102
  } catch {}
7069
7103
  })();
7070
7104
  proc.exited.then((exitCode) => {
7071
- log.debug(`[term] proc exited(${worktreeId}) pid=${proc.pid} code=${exitCode}`);
7072
- if (sessions.get(worktreeId) === session) {
7105
+ log.debug(`[term] proc exited(${attachId}) pid=${proc.pid} code=${exitCode}`);
7106
+ if (sessions.get(attachId) === session) {
7073
7107
  session.onExit?.(exitCode);
7074
- sessions.delete(worktreeId);
7108
+ sessions.delete(attachId);
7075
7109
  } else {
7076
- log.debug(`[term] proc exited(${worktreeId}) stale session, skipping cleanup`);
7110
+ log.debug(`[term] proc exited(${attachId}) stale session, skipping cleanup`);
7077
7111
  }
7078
7112
  killTmuxSession(gName);
7079
7113
  });
7080
7114
  }
7081
- async function detach(worktreeId) {
7082
- const session = sessions.get(worktreeId);
7115
+ async function detach(attachId) {
7116
+ const session = sessions.get(attachId);
7083
7117
  if (!session) {
7084
- log.debug(`[term] detach(${worktreeId}) no session found`);
7118
+ log.debug(`[term] detach(${attachId}) no session found`);
7085
7119
  return;
7086
7120
  }
7087
- log.debug(`[term] detach(${worktreeId}) killing pid=${session.proc.pid} tmux=${session.groupedSessionName}`);
7121
+ log.debug(`[term] detach(${attachId}) killing pid=${session.proc.pid} tmux=${session.groupedSessionName}`);
7088
7122
  session.cancelled = true;
7089
7123
  session.proc.kill();
7090
- sessions.delete(worktreeId);
7124
+ sessions.delete(attachId);
7091
7125
  killTmuxSession(session.groupedSessionName);
7092
7126
  }
7093
- function write(worktreeId, data) {
7094
- const session = sessions.get(worktreeId);
7127
+ function write(attachId, data) {
7128
+ const session = sessions.get(attachId);
7095
7129
  if (!session) {
7096
- log.warn(`[term] write(${worktreeId}) NO SESSION - input dropped (${data.length} bytes)`);
7130
+ log.warn(`[term] write(${attachId}) NO SESSION - input dropped (${data.length} bytes)`);
7097
7131
  return;
7098
7132
  }
7099
7133
  try {
7100
7134
  session.proc.stdin.write(textEncoder.encode(data));
7101
7135
  session.proc.stdin.flush();
7102
7136
  } catch (err) {
7103
- log.error(`[term] write(${worktreeId}) stdin closed`, err);
7137
+ log.error(`[term] write(${attachId}) stdin closed`, err);
7104
7138
  }
7105
7139
  }
7106
- async function sendKeys(worktreeId, hexBytes) {
7107
- const session = sessions.get(worktreeId);
7140
+ async function sendKeys(attachId, hexBytes) {
7141
+ const session = sessions.get(attachId);
7108
7142
  if (!session)
7109
7143
  return;
7110
7144
  const windowTarget = `${session.groupedSessionName}:${session.windowName}`;
7111
7145
  await tmuxExec(["tmux", "send-keys", "-t", windowTarget, "-H", ...hexBytes]);
7112
7146
  }
7113
- async function resize(worktreeId, cols, rows) {
7114
- const session = sessions.get(worktreeId);
7147
+ async function resize(attachId, cols, rows) {
7148
+ const session = sessions.get(attachId);
7115
7149
  if (!session)
7116
7150
  return;
7117
7151
  const windowTarget = `${session.groupedSessionName}:${session.windowName}`;
@@ -7119,32 +7153,32 @@ async function resize(worktreeId, cols, rows) {
7119
7153
  if (result.exitCode !== 0)
7120
7154
  log.warn(`[term] resize failed: ${result.stderr}`);
7121
7155
  }
7122
- function getScrollback(worktreeId) {
7123
- return sessions.get(worktreeId)?.scrollback.join("") ?? "";
7156
+ function getScrollback(attachId) {
7157
+ return sessions.get(attachId)?.scrollback.join("") ?? "";
7124
7158
  }
7125
- function setCallbacks(worktreeId, onData, onExit) {
7126
- const session = sessions.get(worktreeId);
7159
+ function setCallbacks(attachId, onData, onExit) {
7160
+ const session = sessions.get(attachId);
7127
7161
  if (session) {
7128
7162
  session.onData = onData;
7129
7163
  session.onExit = onExit;
7130
7164
  }
7131
7165
  }
7132
- async function selectPane(worktreeId, paneIndex) {
7133
- const session = sessions.get(worktreeId);
7166
+ async function selectPane(attachId, paneIndex) {
7167
+ const session = sessions.get(attachId);
7134
7168
  if (!session) {
7135
- log.debug(`[term] selectPane(${worktreeId}) no session found`);
7169
+ log.debug(`[term] selectPane(${attachId}) no session found`);
7136
7170
  return;
7137
7171
  }
7138
7172
  const target = `${session.groupedSessionName}:${session.windowName}.${paneIndex}`;
7139
- log.debug(`[term] selectPane(${worktreeId}) pane=${paneIndex} target=${target}`);
7173
+ log.debug(`[term] selectPane(${attachId}) pane=${paneIndex} target=${target}`);
7140
7174
  const [r1, r2] = await Promise.all([
7141
7175
  tmuxExec(["tmux", "select-pane", "-t", target]),
7142
7176
  tmuxExec(["tmux", "resize-pane", "-Z", "-t", target])
7143
7177
  ]);
7144
- log.debug(`[term] selectPane(${worktreeId}) select=${r1.exitCode} zoom=${r2.exitCode}`);
7178
+ log.debug(`[term] selectPane(${attachId}) select=${r1.exitCode} zoom=${r2.exitCode}`);
7145
7179
  }
7146
- function clearCallbacks(worktreeId) {
7147
- const session = sessions.get(worktreeId);
7180
+ function clearCallbacks(attachId) {
7181
+ const session = sessions.get(attachId);
7148
7182
  if (session) {
7149
7183
  session.onData = null;
7150
7184
  session.onExit = null;
@@ -7171,6 +7205,10 @@ async function sendPrompt(worktreeId, target, text, paneIndex = 0, preamble) {
7171
7205
  if (paste.exitCode !== 0) {
7172
7206
  return { ok: false, error: `paste-buffer failed${paste.stderr ? `: ${paste.stderr}` : ""}` };
7173
7207
  }
7208
+ const enter = await tmuxExec(["tmux", "send-keys", "-t", paneTarget, "Enter"]);
7209
+ if (enter.exitCode !== 0) {
7210
+ return { ok: false, error: `send-keys Enter failed${enter.stderr ? `: ${enter.stderr}` : ""}` };
7211
+ }
7174
7212
  return { ok: true };
7175
7213
  }
7176
7214
 
@@ -7199,7 +7237,7 @@ async function loadControlToken() {
7199
7237
  import { readFileSync } from "fs";
7200
7238
  import { dirname as dirname2, join, resolve } from "path";
7201
7239
 
7202
- // node_modules/.bun/yaml@2.8.2/node_modules/yaml/dist/index.js
7240
+ // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
7203
7241
  var composer = require_composer();
7204
7242
  var Document = require_Document();
7205
7243
  var Schema = require_Schema();
@@ -7255,7 +7293,8 @@ var DEFAULT_CONFIG = {
7255
7293
  workspace: {
7256
7294
  mainBranch: "main",
7257
7295
  worktreeRoot: "../worktrees",
7258
- defaultAgent: "claude"
7296
+ defaultAgent: "claude",
7297
+ autoPull: { enabled: false, intervalSeconds: 300 }
7259
7298
  },
7260
7299
  profiles: {
7261
7300
  default: {
@@ -7267,8 +7306,8 @@ var DEFAULT_CONFIG = {
7267
7306
  services: [],
7268
7307
  startupEnvs: {},
7269
7308
  integrations: {
7270
- github: { linkedRepos: [] },
7271
- linear: { enabled: true }
7309
+ github: { linkedRepos: [], autoRemoveOnMerge: false },
7310
+ linear: { enabled: true, autoCreateWorktrees: false, createTicketOption: false }
7272
7311
  },
7273
7312
  lifecycleHooks: {},
7274
7313
  autoName: null
@@ -7329,6 +7368,9 @@ function parsePane(raw, index) {
7329
7368
  if (typeof raw.command !== "string" || !raw.command.trim())
7330
7369
  return null;
7331
7370
  pane.command = raw.command.trim();
7371
+ if (typeof raw.workingDir === "string" && raw.workingDir.trim()) {
7372
+ pane.workingDir = raw.workingDir.trim();
7373
+ }
7332
7374
  }
7333
7375
  return pane;
7334
7376
  }
@@ -7426,6 +7468,13 @@ function parseAutoName(raw) {
7426
7468
  ...typeof raw.system_prompt === "string" && raw.system_prompt.trim() ? { systemPrompt: raw.system_prompt.trim() } : {}
7427
7469
  };
7428
7470
  }
7471
+ function parseAutoPull(raw) {
7472
+ if (!isRecord(raw))
7473
+ return DEFAULT_CONFIG.workspace.autoPull;
7474
+ const enabled = typeof raw.enabled === "boolean" ? raw.enabled : false;
7475
+ const interval = typeof raw.intervalSeconds === "number" && Number.isFinite(raw.intervalSeconds) && raw.intervalSeconds >= 30 ? raw.intervalSeconds : 300;
7476
+ return { enabled, intervalSeconds: interval };
7477
+ }
7429
7478
  function parseLinkedRepos(raw) {
7430
7479
  if (!Array.isArray(raw))
7431
7480
  return [];
@@ -7459,17 +7508,22 @@ function parseProjectConfig(parsed) {
7459
7508
  workspace: {
7460
7509
  mainBranch: isRecord(parsed.workspace) && typeof parsed.workspace.mainBranch === "string" ? parsed.workspace.mainBranch : DEFAULT_CONFIG.workspace.mainBranch,
7461
7510
  worktreeRoot: isRecord(parsed.workspace) && typeof parsed.workspace.worktreeRoot === "string" ? parsed.workspace.worktreeRoot : DEFAULT_CONFIG.workspace.worktreeRoot,
7462
- defaultAgent: isRecord(parsed.workspace) ? parseAgentKind(parsed.workspace.defaultAgent) : DEFAULT_CONFIG.workspace.defaultAgent
7511
+ defaultAgent: isRecord(parsed.workspace) ? parseAgentKind(parsed.workspace.defaultAgent) : DEFAULT_CONFIG.workspace.defaultAgent,
7512
+ autoPull: isRecord(parsed.workspace) ? parseAutoPull(parsed.workspace.autoPull) : DEFAULT_CONFIG.workspace.autoPull
7463
7513
  },
7464
7514
  profiles: parseProfiles(parsed.profiles, true),
7465
7515
  services: parseServices(parsed.services),
7466
7516
  startupEnvs: parseStartupEnvs(parsed.startupEnvs),
7467
7517
  integrations: {
7468
7518
  github: {
7469
- linkedRepos: isRecord(parsed.integrations) && isRecord(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github.linkedRepos) : isRecord(parsed.integrations) && Array.isArray(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github) : []
7519
+ linkedRepos: isRecord(parsed.integrations) && isRecord(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github.linkedRepos) : isRecord(parsed.integrations) && Array.isArray(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github) : [],
7520
+ autoRemoveOnMerge: isRecord(parsed.integrations) && isRecord(parsed.integrations.github) && typeof parsed.integrations.github.autoRemoveOnMerge === "boolean" ? parsed.integrations.github.autoRemoveOnMerge : DEFAULT_CONFIG.integrations.github.autoRemoveOnMerge
7470
7521
  },
7471
7522
  linear: {
7472
- enabled: isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.enabled === "boolean" ? parsed.integrations.linear.enabled : DEFAULT_CONFIG.integrations.linear.enabled
7523
+ enabled: isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.enabled === "boolean" ? parsed.integrations.linear.enabled : DEFAULT_CONFIG.integrations.linear.enabled,
7524
+ autoCreateWorktrees: isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.autoCreateWorktrees === "boolean" ? parsed.integrations.linear.autoCreateWorktrees : DEFAULT_CONFIG.integrations.linear.autoCreateWorktrees,
7525
+ createTicketOption: isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.createTicketOption === "boolean" ? parsed.integrations.linear.createTicketOption : DEFAULT_CONFIG.integrations.linear.createTicketOption,
7526
+ ...isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.teamId === "string" && parsed.integrations.linear.teamId.trim() ? { teamId: parsed.integrations.linear.teamId.trim() } : {}
7473
7527
  }
7474
7528
  },
7475
7529
  lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks),
@@ -7479,21 +7533,66 @@ function parseProjectConfig(parsed) {
7479
7533
  function defaultConfig() {
7480
7534
  return parseProjectConfig({});
7481
7535
  }
7536
+ function parseLocalLinearOverlay(parsed) {
7537
+ if (!isRecord(parsed.integrations))
7538
+ return null;
7539
+ const linear = parsed.integrations.linear;
7540
+ if (!isRecord(linear))
7541
+ return null;
7542
+ const overlay = {};
7543
+ if (typeof linear.enabled === "boolean")
7544
+ overlay.enabled = linear.enabled;
7545
+ if (typeof linear.autoCreateWorktrees === "boolean")
7546
+ overlay.autoCreateWorktrees = linear.autoCreateWorktrees;
7547
+ if (typeof linear.createTicketOption === "boolean")
7548
+ overlay.createTicketOption = linear.createTicketOption;
7549
+ if (typeof linear.teamId === "string" && linear.teamId.trim())
7550
+ overlay.teamId = linear.teamId.trim();
7551
+ return Object.keys(overlay).length > 0 ? overlay : null;
7552
+ }
7553
+ function parseLocalGitHubOverlay(parsed) {
7554
+ if (!isRecord(parsed.integrations))
7555
+ return null;
7556
+ const github = parsed.integrations.github;
7557
+ if (!isRecord(github))
7558
+ return null;
7559
+ const overlay = {};
7560
+ if (typeof github.autoRemoveOnMerge === "boolean")
7561
+ overlay.autoRemoveOnMerge = github.autoRemoveOnMerge;
7562
+ return Object.keys(overlay).length > 0 ? overlay : null;
7563
+ }
7564
+ function parseLocalAutoPullOverlay(parsed) {
7565
+ if (!isRecord(parsed.workspace))
7566
+ return null;
7567
+ const autoPull = parsed.workspace.autoPull;
7568
+ if (!isRecord(autoPull))
7569
+ return null;
7570
+ const overlay = {};
7571
+ if (typeof autoPull.enabled === "boolean")
7572
+ overlay.enabled = autoPull.enabled;
7573
+ if (typeof autoPull.intervalSeconds === "number" && Number.isFinite(autoPull.intervalSeconds) && autoPull.intervalSeconds >= 30) {
7574
+ overlay.intervalSeconds = autoPull.intervalSeconds;
7575
+ }
7576
+ return Object.keys(overlay).length > 0 ? overlay : null;
7577
+ }
7482
7578
  function loadLocalProjectConfigOverlay(root) {
7483
7579
  try {
7484
7580
  const text = readLocalConfigFile(root).trim();
7485
7581
  if (!text) {
7486
- return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
7582
+ return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
7487
7583
  }
7488
7584
  const parsed = parseConfigDocument(text);
7489
7585
  const ws = isRecord(parsed.workspace) ? parsed.workspace : null;
7490
7586
  return {
7491
7587
  worktreeRoot: ws && typeof ws.worktreeRoot === "string" ? ws.worktreeRoot : null,
7492
7588
  profiles: parseProfiles(parsed.profiles, false),
7493
- lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks)
7589
+ lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks),
7590
+ linear: parseLocalLinearOverlay(parsed),
7591
+ github: parseLocalGitHubOverlay(parsed),
7592
+ autoPull: parseLocalAutoPullOverlay(parsed)
7494
7593
  };
7495
7594
  } catch {
7496
- return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
7595
+ return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null, github: null, autoPull: null };
7497
7596
  }
7498
7597
  }
7499
7598
  function mergeHookCommand(projectCommand, localCommand) {
@@ -7535,18 +7634,60 @@ function loadConfig(dir, options = {}) {
7535
7634
  projectConfig = defaultConfig();
7536
7635
  }
7537
7636
  const localOverlay = loadLocalProjectConfigOverlay(root);
7637
+ const workspace = localOverlay.worktreeRoot !== null || localOverlay.autoPull ? {
7638
+ ...projectConfig.workspace,
7639
+ ...localOverlay.worktreeRoot !== null ? { worktreeRoot: localOverlay.worktreeRoot } : {},
7640
+ ...localOverlay.autoPull ? { autoPull: { ...projectConfig.workspace.autoPull, ...localOverlay.autoPull } } : {}
7641
+ } : projectConfig.workspace;
7642
+ const hasIntegrationOverlay = localOverlay.linear || localOverlay.github;
7643
+ const integrations = hasIntegrationOverlay ? {
7644
+ ...projectConfig.integrations,
7645
+ ...localOverlay.linear ? { linear: { ...projectConfig.integrations.linear, ...localOverlay.linear } } : {},
7646
+ ...localOverlay.github ? { github: { ...projectConfig.integrations.github, ...localOverlay.github } } : {}
7647
+ } : projectConfig.integrations;
7538
7648
  return {
7539
7649
  ...projectConfig,
7540
- ...localOverlay.worktreeRoot !== null ? {
7541
- workspace: { ...projectConfig.workspace, worktreeRoot: localOverlay.worktreeRoot }
7542
- } : {},
7650
+ workspace,
7543
7651
  profiles: {
7544
7652
  ...cloneProfiles(projectConfig.profiles),
7545
7653
  ...cloneProfiles(localOverlay.profiles)
7546
7654
  },
7547
- lifecycleHooks: mergeLifecycleHooks(projectConfig.lifecycleHooks, localOverlay.lifecycleHooks)
7655
+ lifecycleHooks: mergeLifecycleHooks(projectConfig.lifecycleHooks, localOverlay.lifecycleHooks),
7656
+ integrations
7548
7657
  };
7549
7658
  }
7659
+ async function persistLocalLinearConfig(dir, changes) {
7660
+ const root = projectRoot(dir);
7661
+ const localPath = join(root, ".webmux.local.yaml");
7662
+ let existing = {};
7663
+ try {
7664
+ const text = readFileSync(localPath, "utf8").trim();
7665
+ if (text)
7666
+ existing = parseConfigDocument(text);
7667
+ } catch {}
7668
+ const integrations = isRecord(existing.integrations) ? { ...existing.integrations } : {};
7669
+ const linear = isRecord(integrations.linear) ? { ...integrations.linear } : {};
7670
+ Object.assign(linear, changes);
7671
+ integrations.linear = linear;
7672
+ existing.integrations = integrations;
7673
+ await Bun.write(localPath, $stringify(existing));
7674
+ }
7675
+ async function persistLocalGitHubConfig(dir, changes) {
7676
+ const root = projectRoot(dir);
7677
+ const localPath = join(root, ".webmux.local.yaml");
7678
+ let existing = {};
7679
+ try {
7680
+ const text = readFileSync(localPath, "utf8").trim();
7681
+ if (text)
7682
+ existing = parseConfigDocument(text);
7683
+ } catch {}
7684
+ const integrations = isRecord(existing.integrations) ? { ...existing.integrations } : {};
7685
+ const github = isRecord(integrations.github) ? { ...integrations.github } : {};
7686
+ Object.assign(github, changes);
7687
+ integrations.github = github;
7688
+ existing.integrations = integrations;
7689
+ await Bun.write(localPath, $stringify(existing));
7690
+ }
7550
7691
  function expandTemplate(template, env) {
7551
7692
  return template.replace(/\$\{(\w+)\}/g, (_, key) => env[key] ?? "");
7552
7693
  }
@@ -7601,32 +7742,166 @@ var ASSIGNED_ISSUES_QUERY = `
7601
7742
  }
7602
7743
  }
7603
7744
  `;
7745
+ var VIEWER_QUERY = `
7746
+ query Viewer {
7747
+ viewer {
7748
+ id
7749
+ }
7750
+ }
7751
+ `;
7752
+ var TEAM_STATES_QUERY = `
7753
+ query TeamStates($teamId: String!) {
7754
+ team(id: $teamId) {
7755
+ states {
7756
+ nodes {
7757
+ id
7758
+ name
7759
+ type
7760
+ }
7761
+ }
7762
+ }
7763
+ }
7764
+ `;
7765
+ var ISSUE_CREATE_MUTATION = `
7766
+ mutation IssueCreate($input: IssueCreateInput!) {
7767
+ issueCreate(input: $input) {
7768
+ success
7769
+ issue {
7770
+ id
7771
+ identifier
7772
+ title
7773
+ url
7774
+ branchName
7775
+ }
7776
+ }
7777
+ }
7778
+ `;
7779
+ function gqlErrorMessage(raw) {
7780
+ return raw.errors && raw.errors.length > 0 ? raw.errors.map((error) => error.message).join("; ") : null;
7781
+ }
7604
7782
  function parseIssuesResponse(raw) {
7605
- if (raw.errors && raw.errors.length > 0) {
7606
- return { ok: false, error: raw.errors.map((e) => e.message).join("; ") };
7783
+ const error = gqlErrorMessage(raw);
7784
+ if (error) {
7785
+ return { ok: false, error };
7607
7786
  }
7608
7787
  if (!raw.data) {
7609
7788
  return { ok: false, error: "No data in response" };
7610
7789
  }
7611
- const nodes = raw.data.viewer.assignedIssues.nodes;
7612
- const issues = nodes.map((n) => ({
7613
- id: n.id,
7614
- identifier: n.identifier,
7615
- title: n.title,
7616
- description: n.description,
7617
- priority: n.priority,
7618
- priorityLabel: n.priorityLabel,
7619
- url: n.url,
7620
- branchName: n.branchName,
7621
- dueDate: n.dueDate,
7622
- updatedAt: n.updatedAt,
7623
- state: n.state,
7624
- team: n.team,
7625
- labels: n.labels.nodes,
7626
- project: n.project?.name ?? null
7790
+ const issues = raw.data.viewer.assignedIssues.nodes.map((node) => ({
7791
+ id: node.id,
7792
+ identifier: node.identifier,
7793
+ title: node.title,
7794
+ description: node.description,
7795
+ priority: node.priority,
7796
+ priorityLabel: node.priorityLabel,
7797
+ url: node.url,
7798
+ branchName: node.branchName,
7799
+ dueDate: node.dueDate,
7800
+ updatedAt: node.updatedAt,
7801
+ state: node.state,
7802
+ team: node.team,
7803
+ labels: node.labels.nodes,
7804
+ project: node.project?.name ?? null
7627
7805
  }));
7628
7806
  return { ok: true, data: issues };
7629
7807
  }
7808
+ function buildLinearIssuesResponse(input) {
7809
+ if (!input.integrationEnabled) {
7810
+ return {
7811
+ ok: true,
7812
+ data: {
7813
+ availability: "disabled",
7814
+ issues: []
7815
+ }
7816
+ };
7817
+ }
7818
+ if (!input.apiKey?.trim()) {
7819
+ return {
7820
+ ok: true,
7821
+ data: {
7822
+ availability: "missing_api_key",
7823
+ issues: []
7824
+ }
7825
+ };
7826
+ }
7827
+ if (!input.fetchResult) {
7828
+ return { ok: false, error: "Linear fetch result required when LINEAR_API_KEY is set" };
7829
+ }
7830
+ if (!input.fetchResult.ok) {
7831
+ return input.fetchResult;
7832
+ }
7833
+ return {
7834
+ ok: true,
7835
+ data: {
7836
+ availability: "ready",
7837
+ issues: input.fetchResult.data
7838
+ }
7839
+ };
7840
+ }
7841
+ function parseViewerIdResponse(raw) {
7842
+ const error = gqlErrorMessage(raw);
7843
+ if (error) {
7844
+ return { ok: false, error };
7845
+ }
7846
+ const viewerId = raw.data?.viewer.id;
7847
+ if (!viewerId) {
7848
+ return { ok: false, error: "No viewer id in response" };
7849
+ }
7850
+ return { ok: true, data: viewerId };
7851
+ }
7852
+ function parseInProgressStateIdResponse(raw) {
7853
+ const error = gqlErrorMessage(raw);
7854
+ if (error) {
7855
+ return { ok: false, error };
7856
+ }
7857
+ const states = raw.data?.team?.states.nodes;
7858
+ if (!states) {
7859
+ return { ok: false, error: "No team states in response" };
7860
+ }
7861
+ const preferredState = states.find((state) => state.type === "started" && state.name.trim().toLowerCase() === "in progress");
7862
+ if (preferredState) {
7863
+ return { ok: true, data: preferredState.id };
7864
+ }
7865
+ const startedState = states.find((state) => state.type === "started");
7866
+ if (!startedState) {
7867
+ return { ok: false, error: "No started workflow state found for team" };
7868
+ }
7869
+ return { ok: true, data: startedState.id };
7870
+ }
7871
+ function parseIssueCreateResponse(raw) {
7872
+ const error = gqlErrorMessage(raw);
7873
+ if (error) {
7874
+ return { ok: false, error };
7875
+ }
7876
+ const payload = raw.data?.issueCreate;
7877
+ if (!payload) {
7878
+ return { ok: false, error: "No issueCreate payload in response" };
7879
+ }
7880
+ if (!payload.success || !payload.issue) {
7881
+ return { ok: false, error: "Linear issue creation was not successful" };
7882
+ }
7883
+ if (!payload.issue.branchName) {
7884
+ return { ok: false, error: "Linear issue did not return a branch name" };
7885
+ }
7886
+ return {
7887
+ ok: true,
7888
+ data: {
7889
+ id: payload.issue.id,
7890
+ identifier: payload.issue.identifier,
7891
+ title: payload.issue.title,
7892
+ url: payload.issue.url,
7893
+ branchName: payload.issue.branchName
7894
+ }
7895
+ };
7896
+ }
7897
+ function deriveLinearIssueTitle(explicitTitle, prompt) {
7898
+ const trimmedTitle = explicitTitle?.trim();
7899
+ if (trimmedTitle) {
7900
+ return trimmedTitle;
7901
+ }
7902
+ const firstPromptLine = prompt?.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
7903
+ return firstPromptLine ?? null;
7904
+ }
7630
7905
  function branchMatchesIssue(worktreeBranch, issueBranchName) {
7631
7906
  if (!worktreeBranch || !issueBranchName)
7632
7907
  return false;
@@ -7650,15 +7925,13 @@ function branchMatchesIssue(worktreeBranch, issueBranchName) {
7650
7925
  }
7651
7926
  var CACHE_TTL_MS = 300000;
7652
7927
  var issueCache = null;
7653
- async function fetchAssignedIssues() {
7928
+ var viewerIdCache = null;
7929
+ var inProgressStateIdCache = new Map;
7930
+ async function postLinearGraphql(query, variables) {
7654
7931
  const apiKey = Bun.env.LINEAR_API_KEY;
7655
7932
  if (!apiKey) {
7656
7933
  return { ok: false, error: "LINEAR_API_KEY not set" };
7657
7934
  }
7658
- const now = Date.now();
7659
- if (issueCache && now < issueCache.expiry) {
7660
- return issueCache.data;
7661
- }
7662
7935
  try {
7663
7936
  const res = await fetch("https://api.linear.app/graphql", {
7664
7937
  method: "POST",
@@ -7666,33 +7939,111 @@ async function fetchAssignedIssues() {
7666
7939
  "Content-Type": "application/json",
7667
7940
  Authorization: apiKey
7668
7941
  },
7669
- body: JSON.stringify({ query: ASSIGNED_ISSUES_QUERY })
7942
+ body: JSON.stringify(variables ? { query, variables } : { query })
7670
7943
  });
7671
7944
  if (!res.ok) {
7672
7945
  const text = await res.text();
7673
- const result2 = { ok: false, error: `Linear API ${res.status}: ${text.slice(0, 200)}` };
7674
- return result2;
7675
- }
7676
- const json = await res.json();
7677
- const result = parseIssuesResponse(json);
7678
- if (result.ok) {
7679
- issueCache = { data: result, expiry: now + CACHE_TTL_MS };
7680
- log.debug(`[linear] fetched ${result.data.length} assigned issues`);
7681
- } else {
7682
- log.error(`[linear] GraphQL error: ${result.error}`);
7946
+ return { ok: false, error: `Linear API ${res.status}: ${text.slice(0, 200)}` };
7683
7947
  }
7684
- return result;
7948
+ return {
7949
+ ok: true,
7950
+ data: await res.json()
7951
+ };
7685
7952
  } catch (err) {
7686
7953
  const msg = err instanceof Error ? err.message : String(err);
7687
- log.error(`[linear] fetch failed: ${msg}`);
7688
7954
  return { ok: false, error: msg };
7689
7955
  }
7690
7956
  }
7957
+ async function fetchViewerId() {
7958
+ if (viewerIdCache) {
7959
+ return { ok: true, data: viewerIdCache };
7960
+ }
7961
+ const response = await postLinearGraphql(VIEWER_QUERY);
7962
+ if (!response.ok) {
7963
+ log.error(`[linear] viewer fetch failed: ${response.error}`);
7964
+ return { ok: false, error: response.error };
7965
+ }
7966
+ const result = parseViewerIdResponse(response.data);
7967
+ if (!result.ok) {
7968
+ log.error(`[linear] viewer GraphQL error: ${result.error}`);
7969
+ return result;
7970
+ }
7971
+ viewerIdCache = result.data;
7972
+ return result;
7973
+ }
7974
+ async function fetchInProgressStateId(teamId) {
7975
+ const cachedStateId = inProgressStateIdCache.get(teamId);
7976
+ if (cachedStateId) {
7977
+ return { ok: true, data: cachedStateId };
7978
+ }
7979
+ const response = await postLinearGraphql(TEAM_STATES_QUERY, { teamId });
7980
+ if (!response.ok) {
7981
+ log.error(`[linear] team states fetch failed: ${response.error}`);
7982
+ return { ok: false, error: response.error };
7983
+ }
7984
+ const result = parseInProgressStateIdResponse(response.data);
7985
+ if (!result.ok) {
7986
+ log.error(`[linear] team states GraphQL error: ${result.error}`);
7987
+ return result;
7988
+ }
7989
+ inProgressStateIdCache.set(teamId, result.data);
7990
+ return result;
7991
+ }
7992
+ async function fetchAssignedIssues(options) {
7993
+ const now = Date.now();
7994
+ if (!options?.skipCache && issueCache && now < issueCache.expiry) {
7995
+ return issueCache.data;
7996
+ }
7997
+ const response = await postLinearGraphql(ASSIGNED_ISSUES_QUERY);
7998
+ if (!response.ok) {
7999
+ log.error(`[linear] fetch failed: ${response.error}`);
8000
+ return { ok: false, error: response.error };
8001
+ }
8002
+ const result = parseIssuesResponse(response.data);
8003
+ if (result.ok) {
8004
+ issueCache = { data: result, expiry: now + CACHE_TTL_MS };
8005
+ log.debug(`[linear] fetched ${result.data.length} assigned issues`);
8006
+ } else {
8007
+ log.error(`[linear] GraphQL error: ${result.error}`);
8008
+ }
8009
+ return result;
8010
+ }
8011
+ async function createLinearIssue(input) {
8012
+ const viewerResult = await fetchViewerId();
8013
+ if (!viewerResult.ok) {
8014
+ return { ok: false, error: viewerResult.error };
8015
+ }
8016
+ const stateResult = await fetchInProgressStateId(input.teamId);
8017
+ if (!stateResult.ok) {
8018
+ return { ok: false, error: stateResult.error };
8019
+ }
8020
+ const response = await postLinearGraphql(ISSUE_CREATE_MUTATION, {
8021
+ input: {
8022
+ title: input.title,
8023
+ description: input.description,
8024
+ teamId: input.teamId,
8025
+ assigneeId: viewerResult.data,
8026
+ stateId: stateResult.data
8027
+ }
8028
+ });
8029
+ if (!response.ok) {
8030
+ log.error(`[linear] create failed: ${response.error}`);
8031
+ return { ok: false, error: response.error };
8032
+ }
8033
+ const result = parseIssueCreateResponse(response.data);
8034
+ if (result.ok) {
8035
+ issueCache = null;
8036
+ log.debug(`[linear] created issue ${result.data.identifier} branch=${result.data.branchName}`);
8037
+ } else {
8038
+ log.error(`[linear] issueCreate error: ${result.error}`);
8039
+ }
8040
+ return result;
8041
+ }
7691
8042
 
7692
8043
  // backend/src/services/lifecycle-service.ts
7693
8044
  import { randomUUID as randomUUID2 } from "crypto";
7694
8045
  import { mkdir as mkdir4 } from "fs/promises";
7695
- import { dirname as dirname4, resolve as resolve4 } from "path";
8046
+ import { dirname as dirname4, resolve as resolve5 } from "path";
7696
8047
 
7697
8048
  // backend/src/adapters/agent-runtime.ts
7698
8049
  import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
@@ -7815,7 +8166,7 @@ function isPrComment(raw) {
7815
8166
  function isCiCheck(raw) {
7816
8167
  if (!isRecord2(raw))
7817
8168
  return false;
7818
- return typeof raw.name === "string" && (raw.status === "pending" || raw.status === "success" || raw.status === "failed" || raw.status === "skipped") && typeof raw.url === "string" && (raw.runId === null || typeof raw.runId === "number");
8169
+ return typeof raw.name === "string" && (raw.status === "pending" || raw.status === "success" || raw.status === "failed" || raw.status === "skipped") && (raw.url === null || typeof raw.url === "string") && (raw.runId === null || typeof raw.runId === "number");
7819
8170
  }
7820
8171
  function isPrEntry(raw) {
7821
8172
  if (!isRecord2(raw))
@@ -8315,20 +8666,31 @@ function buildDockerAgentPaneCommand(input) {
8315
8666
  }
8316
8667
 
8317
8668
  // backend/src/services/session-service.ts
8669
+ import { resolve as resolve3 } from "path";
8670
+ function quoteShell2(value) {
8671
+ return `'${value.replaceAll("'", "'\\''")}'`;
8672
+ }
8318
8673
  function resolvePaneCwd(template, ctx) {
8319
8674
  return template.cwd === "repo" ? ctx.repoRoot : ctx.worktreePath;
8320
8675
  }
8321
- function resolvePaneStartupCommand(template, commands) {
8676
+ function buildCommandPaneStartupCommand(template, ctx) {
8677
+ if (!template.command) {
8678
+ throw new Error(`Pane "${template.id}" is kind=command but has no command`);
8679
+ }
8680
+ if (!template.workingDir) {
8681
+ return template.command;
8682
+ }
8683
+ const workingDir = resolve3(resolvePaneCwd(template, ctx), template.workingDir);
8684
+ return `cd -- ${quoteShell2(workingDir)} && ${template.command}`;
8685
+ }
8686
+ function resolvePaneStartupCommand(template, ctx) {
8322
8687
  switch (template.kind) {
8323
8688
  case "agent":
8324
- return commands.agent;
8689
+ return ctx.paneCommands.agent;
8325
8690
  case "shell":
8326
8691
  return;
8327
8692
  case "command":
8328
- if (!template.command) {
8329
- throw new Error(`Pane "${template.id}" is kind=command but has no command`);
8330
- }
8331
- return template.command;
8693
+ return buildCommandPaneStartupCommand(template, ctx);
8332
8694
  }
8333
8695
  }
8334
8696
  function planSessionLayout(projectRoot2, branch, templates, ctx) {
@@ -8336,7 +8698,7 @@ function planSessionLayout(projectRoot2, branch, templates, ctx) {
8336
8698
  throw new Error("At least one pane template is required");
8337
8699
  }
8338
8700
  const panes = templates.map((template, index) => {
8339
- const startupCommand = resolvePaneStartupCommand(template, ctx.paneCommands);
8701
+ const startupCommand = resolvePaneStartupCommand(template, ctx);
8340
8702
  return {
8341
8703
  id: template.id,
8342
8704
  index,
@@ -8398,7 +8760,7 @@ import { randomUUID } from "crypto";
8398
8760
 
8399
8761
  // backend/src/adapters/git.ts
8400
8762
  import { rmSync } from "fs";
8401
- import { resolve as resolve3 } from "path";
8763
+ import { resolve as resolve4 } from "path";
8402
8764
  function runGit(args, cwd) {
8403
8765
  const result = Bun.spawnSync(["git", ...args], {
8404
8766
  cwd,
@@ -8432,8 +8794,8 @@ function errorMessage(error) {
8432
8794
  return error instanceof Error ? error.message : String(error);
8433
8795
  }
8434
8796
  function isRegisteredWorktree(entries, worktreePath) {
8435
- const resolvedPath = resolve3(worktreePath);
8436
- return entries.some((entry) => resolve3(entry.path) === resolvedPath);
8797
+ const resolvedPath = resolve4(worktreePath);
8798
+ return entries.some((entry) => resolve4(entry.path) === resolvedPath);
8437
8799
  }
8438
8800
  function removeDirectory(path) {
8439
8801
  rmSync(path, {
@@ -8456,11 +8818,11 @@ function currentCheckoutRef(cwd) {
8456
8818
  }
8457
8819
  function resolveWorktreeRoot(cwd) {
8458
8820
  const output = runGit(["rev-parse", "--show-toplevel"], cwd);
8459
- return resolve3(cwd, output);
8821
+ return resolve4(cwd, output);
8460
8822
  }
8461
8823
  function resolveWorktreeGitDir(cwd) {
8462
8824
  const output = runGit(["rev-parse", "--git-dir"], cwd);
8463
- return resolve3(cwd, output);
8825
+ return resolve4(cwd, output);
8464
8826
  }
8465
8827
  function parseGitWorktreePorcelain(output) {
8466
8828
  const entries = [];
@@ -8518,6 +8880,14 @@ function listLocalGitBranches(cwd) {
8518
8880
  return output.split(`
8519
8881
  `).map((line) => line.trim()).filter((line) => line.length > 0);
8520
8882
  }
8883
+ function listRemoteGitBranches(cwd) {
8884
+ try {
8885
+ runGit(["fetch", "--prune", "origin"], cwd);
8886
+ } catch {}
8887
+ const output = runGit(["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin"], cwd);
8888
+ return output.split(`
8889
+ `).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^origin\//, "")).filter((name) => name !== "HEAD");
8890
+ }
8521
8891
  function readGitWorktreeStatus(cwd) {
8522
8892
  const dirtyOutput = runGit(["status", "--porcelain"], cwd);
8523
8893
  const commit = tryRunGit(["rev-parse", "HEAD"], cwd);
@@ -8565,6 +8935,9 @@ class BunGitGateway {
8565
8935
  listLocalBranches(cwd) {
8566
8936
  return listLocalGitBranches(cwd);
8567
8937
  }
8938
+ listRemoteBranches(cwd) {
8939
+ return listRemoteGitBranches(cwd);
8940
+ }
8568
8941
  readWorktreeStatus(cwd) {
8569
8942
  return readGitWorktreeStatus(cwd);
8570
8943
  }
@@ -8639,6 +9012,15 @@ class BunGitGateway {
8639
9012
  };
8640
9013
  });
8641
9014
  }
9015
+ fetchBranch(repoRoot, remote, branch) {
9016
+ return tryRunGit(["fetch", remote, branch], repoRoot);
9017
+ }
9018
+ fastForwardMerge(repoRoot, ref) {
9019
+ return tryRunGit(["merge", "--ff-only", ref], repoRoot);
9020
+ }
9021
+ hardReset(repoRoot, ref) {
9022
+ return tryRunGit(["reset", "--hard", ref], repoRoot);
9023
+ }
8642
9024
  }
8643
9025
 
8644
9026
  // backend/src/domain/model.ts
@@ -8693,6 +9075,7 @@ async function initializeManagedWorktree(opts) {
8693
9075
  schemaVersion: WORKTREE_META_SCHEMA_VERSION,
8694
9076
  worktreeId: opts.worktreeId ?? randomUUID(),
8695
9077
  branch: opts.branch,
9078
+ ...opts.baseBranch ? { baseBranch: opts.baseBranch } : {},
8696
9079
  createdAt,
8697
9080
  profile: opts.profile,
8698
9081
  agent: opts.agent,
@@ -8739,6 +9122,7 @@ async function createManagedWorktree(opts, deps = {}) {
8739
9122
  const initialized = await initializeManagedWorktree({
8740
9123
  gitDir,
8741
9124
  branch: opts.branch,
9125
+ baseBranch: opts.baseBranch,
8742
9126
  profile: opts.profile,
8743
9127
  agent: opts.agent,
8744
9128
  runtime: opts.runtime,
@@ -8811,19 +9195,34 @@ class LifecycleService {
8811
9195
  }
8812
9196
  async createWorktree(input) {
8813
9197
  const mode = input.mode ?? "new";
9198
+ const requestedBaseBranch = input.baseBranch?.trim();
9199
+ if (requestedBaseBranch && !isValidBranchName(requestedBaseBranch)) {
9200
+ throw new LifecycleError("Invalid base branch name", 400);
9201
+ }
9202
+ if (requestedBaseBranch && mode === "existing") {
9203
+ throw new LifecycleError("Base branch is only supported for new worktrees", 400);
9204
+ }
8814
9205
  const branch = await this.resolveBranch(input.branch, input.prompt, mode);
9206
+ if (requestedBaseBranch && requestedBaseBranch === branch) {
9207
+ throw new LifecycleError("Base branch must differ from branch name", 400);
9208
+ }
9209
+ const baseBranch = mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
8815
9210
  this.ensureBranchAvailable(branch, mode);
8816
9211
  const { profileName, profile } = this.resolveProfile(input.profile);
8817
9212
  const agent = this.resolveAgent(input.agent);
8818
9213
  const worktreePath = this.resolveWorktreePath(branch);
9214
+ const createProgressBase = {
9215
+ branch,
9216
+ ...baseBranch ? { baseBranch } : {},
9217
+ path: worktreePath,
9218
+ profile: profileName,
9219
+ agent
9220
+ };
8819
9221
  const deleteBranchOnRollback = mode === "new";
8820
9222
  let initialized = null;
8821
9223
  try {
8822
9224
  await this.reportCreateProgress({
8823
- branch,
8824
- path: worktreePath,
8825
- profile: profileName,
8826
- agent,
9225
+ ...createProgressBase,
8827
9226
  phase: "creating_worktree"
8828
9227
  });
8829
9228
  await mkdir4(dirname4(worktreePath), { recursive: true });
@@ -8832,7 +9231,7 @@ class LifecycleService {
8832
9231
  worktreePath,
8833
9232
  branch,
8834
9233
  mode,
8835
- ...mode === "new" ? { baseBranch: this.deps.config.workspace.mainBranch } : {},
9234
+ ...baseBranch ? { baseBranch } : {},
8836
9235
  profile: profileName,
8837
9236
  agent,
8838
9237
  runtime: profile.runtime,
@@ -8846,10 +9245,7 @@ class LifecycleService {
8846
9245
  git: this.deps.git
8847
9246
  });
8848
9247
  await this.reportCreateProgress({
8849
- branch,
8850
- path: worktreePath,
8851
- profile: profileName,
8852
- agent,
9248
+ ...createProgressBase,
8853
9249
  phase: "running_post_create_hook"
8854
9250
  });
8855
9251
  await this.runLifecycleHook({
@@ -8864,10 +9260,7 @@ class LifecycleService {
8864
9260
  worktreePath
8865
9261
  });
8866
9262
  await this.reportCreateProgress({
8867
- branch,
8868
- path: worktreePath,
8869
- profile: profileName,
8870
- agent,
9263
+ ...createProgressBase,
8871
9264
  phase: "preparing_runtime"
8872
9265
  });
8873
9266
  await ensureAgentRuntimeArtifacts({
@@ -8875,10 +9268,7 @@ class LifecycleService {
8875
9268
  worktreePath
8876
9269
  });
8877
9270
  await this.reportCreateProgress({
8878
- branch,
8879
- path: worktreePath,
8880
- profile: profileName,
8881
- agent,
9271
+ ...createProgressBase,
8882
9272
  phase: "starting_session"
8883
9273
  });
8884
9274
  await this.materializeRuntimeSession({
@@ -8891,13 +9281,10 @@ class LifecycleService {
8891
9281
  launchMode: "fresh"
8892
9282
  });
8893
9283
  await this.reportCreateProgress({
8894
- branch,
8895
- path: worktreePath,
8896
- profile: profileName,
8897
- agent,
9284
+ ...createProgressBase,
8898
9285
  phase: "reconciling"
8899
9286
  });
8900
- await this.deps.reconciliation.reconcile(this.deps.projectRoot);
9287
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
8901
9288
  return {
8902
9289
  branch,
8903
9290
  worktreeId: initialized.meta.worktreeId
@@ -8932,7 +9319,7 @@ class LifecycleService {
8932
9319
  worktreePath: resolved.entry.path,
8933
9320
  launchMode
8934
9321
  });
8935
- await this.deps.reconciliation.reconcile(this.deps.projectRoot);
9322
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
8936
9323
  return {
8937
9324
  branch,
8938
9325
  worktreeId: initialized.meta.worktreeId
@@ -8945,7 +9332,7 @@ class LifecycleService {
8945
9332
  try {
8946
9333
  const resolved = await this.resolveExistingWorktree(branch);
8947
9334
  this.deps.tmux.killWindow(buildProjectSessionName(this.deps.projectRoot), buildWorktreeWindowName(branch));
8948
- await this.deps.reconciliation.reconcile(this.deps.projectRoot);
9335
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
8949
9336
  } catch (error) {
8950
9337
  throw this.wrapOperationError(error);
8951
9338
  }
@@ -8992,8 +9379,13 @@ class LifecycleService {
8992
9379
  }
8993
9380
  listAvailableBranches() {
8994
9381
  const localBranches = this.listLocalBranches().filter((branch) => isValidBranchName(branch));
9382
+ const remoteBranches = this.listRemoteBranches().filter((branch) => isValidBranchName(branch));
8995
9383
  const checkedOutBranches = this.listCheckedOutBranches();
8996
- return localBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
9384
+ const allBranches = [...new Set([...localBranches, ...remoteBranches])];
9385
+ return allBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
9386
+ }
9387
+ listBaseBranches() {
9388
+ return this.listLocalBranches().filter((branch) => isValidBranchName(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
8997
9389
  }
8998
9390
  async resolveBranch(rawBranch, prompt, mode) {
8999
9391
  const explicitBranch = rawBranch?.trim();
@@ -9061,17 +9453,20 @@ class LifecycleService {
9061
9453
  return allocateServicePorts(metas, this.deps.config.services);
9062
9454
  }
9063
9455
  resolveWorktreePath(branch) {
9064
- return resolve4(this.deps.projectRoot, this.deps.config.workspace.worktreeRoot, branch);
9456
+ return resolve5(this.deps.projectRoot, this.deps.config.workspace.worktreeRoot, branch);
9065
9457
  }
9066
9458
  listLocalBranches() {
9067
- return this.deps.git.listLocalBranches(resolve4(this.deps.projectRoot));
9459
+ return this.deps.git.listLocalBranches(resolve5(this.deps.projectRoot));
9460
+ }
9461
+ listRemoteBranches() {
9462
+ return this.deps.git.listRemoteBranches(resolve5(this.deps.projectRoot));
9068
9463
  }
9069
9464
  listCheckedOutBranches() {
9070
- return new Set(this.deps.git.listWorktrees(resolve4(this.deps.projectRoot)).filter((entry) => !entry.bare && entry.branch !== null).map((entry) => entry.branch));
9465
+ return new Set(this.deps.git.listWorktrees(resolve5(this.deps.projectRoot)).filter((entry) => !entry.bare && entry.branch !== null).map((entry) => entry.branch));
9071
9466
  }
9072
9467
  listProjectWorktrees() {
9073
- const projectRoot2 = resolve4(this.deps.projectRoot);
9074
- return this.deps.git.listWorktrees(projectRoot2).filter((entry) => !entry.bare && resolve4(entry.path) !== projectRoot2);
9468
+ const projectRoot2 = resolve5(this.deps.projectRoot);
9469
+ return this.deps.git.listWorktrees(projectRoot2).filter((entry) => !entry.bare && resolve5(entry.path) !== projectRoot2);
9075
9470
  }
9076
9471
  async readManagedMetas() {
9077
9472
  const metas = await Promise.all(this.listProjectWorktrees().map(async (entry) => {
@@ -9271,15 +9666,15 @@ class LifecycleService {
9271
9666
  deleteBranch: true,
9272
9667
  deleteBranchForce: true
9273
9668
  }, this.deps.git);
9274
- await this.deps.reconciliation.reconcile(this.deps.projectRoot);
9669
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
9275
9670
  }
9276
9671
  async runLifecycleHook(input) {
9277
- console.debug(`[lifecycle-hook] name=${input.name} command=${input.command ?? "UNDEFINED"} meta=${input.meta ? "present" : "NULL"} cwd=${input.worktreePath}`);
9672
+ log.debug(`[lifecycle-hook] name=${input.name} command=${input.command ?? "UNDEFINED"} meta=${input.meta ? "present" : "NULL"} cwd=${input.worktreePath}`);
9278
9673
  if (!input.command || !input.meta) {
9279
- console.debug(`[lifecycle-hook] SKIPPING ${input.name}: command=${!!input.command} meta=${!!input.meta}`);
9674
+ log.debug(`[lifecycle-hook] SKIPPING ${input.name}: command=${!!input.command} meta=${!!input.meta}`);
9280
9675
  return;
9281
9676
  }
9282
- console.debug(`[lifecycle-hook] RUNNING ${input.name}: ${input.command} in ${input.worktreePath}`);
9677
+ log.debug(`[lifecycle-hook] RUNNING ${input.name}: ${input.command} in ${input.worktreePath}`);
9283
9678
  const dotenvValues = await loadDotenvLocal(input.worktreePath);
9284
9679
  await this.deps.hooks.run({
9285
9680
  name: input.name,
@@ -9289,7 +9684,7 @@ class LifecycleService {
9289
9684
  WEBMUX_WORKTREE_PATH: input.worktreePath
9290
9685
  }, dotenvValues)
9291
9686
  });
9292
- console.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
9687
+ log.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
9293
9688
  }
9294
9689
  async reportCreateProgress(progress) {
9295
9690
  await this.deps.onCreateProgress?.(progress);
@@ -9305,6 +9700,117 @@ class LifecycleService {
9305
9700
  }
9306
9701
  }
9307
9702
 
9703
+ // backend/src/services/native-terminal-service.ts
9704
+ function quoteShell3(value) {
9705
+ return `'${value.replaceAll("'", "'\\''")}'`;
9706
+ }
9707
+ function sanitizeSessionSuffix(value) {
9708
+ const sanitized = value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[.-]+|[.-]+$/g, "");
9709
+ const trimmed = sanitized.slice(0, 24);
9710
+ return trimmed || "x";
9711
+ }
9712
+ function buildNativeTerminalTmuxCommand(env) {
9713
+ const socket = env.WEBMUX_ISOLATED_TMUX_SOCKET;
9714
+ const config = env.WEBMUX_ISOLATED_TMUX_CONFIG;
9715
+ if (socket && config) {
9716
+ return `tmux -L ${quoteShell3(socket)} -f ${quoteShell3(config)}`;
9717
+ }
9718
+ if (socket) {
9719
+ return `tmux -L ${quoteShell3(socket)}`;
9720
+ }
9721
+ return "tmux";
9722
+ }
9723
+ function buildNativeTerminalLaunch(input) {
9724
+ const { branch, state, tmuxCommand } = input;
9725
+ if (!state || !state.git.exists) {
9726
+ return {
9727
+ ok: false,
9728
+ reason: "not_found",
9729
+ message: `Worktree not found: ${branch}`
9730
+ };
9731
+ }
9732
+ if (!state.session.exists || !state.session.sessionName) {
9733
+ return {
9734
+ ok: false,
9735
+ reason: "closed",
9736
+ message: `No open tmux window found for worktree: ${branch}`
9737
+ };
9738
+ }
9739
+ const sessionPrefix = input.sessionPrefix ?? "wm-native-launch-";
9740
+ const groupedSessionPrefix = `${sessionPrefix}${sanitizeSessionSuffix(state.worktreeId)}`;
9741
+ const attachScript = [
9742
+ `g_name="${groupedSessionPrefix}-${"$"}$-$(date +%s)"`,
9743
+ `owner_session_name=${quoteShell3(state.session.sessionName)}`,
9744
+ `window_name=${quoteShell3(state.session.windowName)}`,
9745
+ `grouped_window_target="${"$"}g_name:${"$"}window_name"`,
9746
+ `grouped_pane_target="${"$"}grouped_window_target.0"`,
9747
+ `cleanup() { ${tmuxCommand} kill-session -t "${"$"}g_name" >/dev/null 2>&1 || true; }`,
9748
+ "cleanup",
9749
+ `${tmuxCommand} new-session -d -s "${"$"}g_name" -t "${"$"}owner_session_name"`,
9750
+ `${tmuxCommand} set-option -t "${"$"}owner_session_name" window-size latest`,
9751
+ `${tmuxCommand} set-option -t "${"$"}g_name" mouse on`,
9752
+ `${tmuxCommand} set-option -t "${"$"}g_name" set-clipboard on`,
9753
+ `${tmuxCommand} select-window -t "${"$"}grouped_window_target"`,
9754
+ `if [ "$(${tmuxCommand} display-message -t "${"$"}grouped_window_target" -p '#{window_zoomed_flag}')" = "1" ]; then ${tmuxCommand} resize-pane -Z -t "${"$"}grouped_window_target"; fi`,
9755
+ `${tmuxCommand} select-pane -t "${"$"}grouped_pane_target"`,
9756
+ "trap cleanup EXIT INT TERM",
9757
+ `exec ${tmuxCommand} attach-session -t "${"$"}g_name"`
9758
+ ].join(" && ");
9759
+ return {
9760
+ ok: true,
9761
+ data: {
9762
+ worktreeId: state.worktreeId,
9763
+ branch: state.branch,
9764
+ path: state.path,
9765
+ shellCommand: `/bin/sh -lc ${quoteShell3(attachScript)}`
9766
+ }
9767
+ };
9768
+ }
9769
+
9770
+ // backend/src/lib/async.ts
9771
+ async function mapWithConcurrency(items, limit, fn) {
9772
+ const results = new Array(items.length);
9773
+ let next = 0;
9774
+ const concurrency = Math.max(1, Math.min(limit, items.length || 1));
9775
+ async function worker() {
9776
+ while (next < items.length) {
9777
+ const index = next++;
9778
+ results[index] = await fn(items[index]);
9779
+ }
9780
+ }
9781
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
9782
+ return results;
9783
+ }
9784
+ function startSerializedInterval(run, intervalMs, deps = {}) {
9785
+ const scheduleEvery = deps.scheduleEvery ?? ((handler, ms) => setInterval(handler, ms));
9786
+ const cancelSchedule = deps.cancelSchedule ?? ((handle2) => clearInterval(handle2));
9787
+ let running = false;
9788
+ let rerunRequested = false;
9789
+ let stopped = false;
9790
+ const execute = () => {
9791
+ if (stopped)
9792
+ return;
9793
+ if (running) {
9794
+ rerunRequested = true;
9795
+ return;
9796
+ }
9797
+ running = true;
9798
+ Promise.resolve().then(run).finally(() => {
9799
+ running = false;
9800
+ if (stopped || !rerunRequested)
9801
+ return;
9802
+ rerunRequested = false;
9803
+ execute();
9804
+ });
9805
+ };
9806
+ execute();
9807
+ const handle = scheduleEvery(execute, intervalMs);
9808
+ return () => {
9809
+ stopped = true;
9810
+ cancelSchedule(handle);
9811
+ };
9812
+ }
9813
+
9308
9814
  // backend/src/services/pr-service.ts
9309
9815
  var PR_FETCH_LIMIT = 50;
9310
9816
  var GH_TIMEOUT_MS = 15000;
@@ -9314,17 +9820,31 @@ var etagCache = new Map;
9314
9820
  function summarizeChecks(checks) {
9315
9821
  if (!checks || checks.length === 0)
9316
9822
  return "none";
9317
- const allDone = checks.every((c) => c.status === "COMPLETED");
9823
+ const allDone = checks.every((c) => c.__typename === "StatusContext" ? c.state !== "PENDING" && c.state !== "EXPECTED" : c.status === "COMPLETED");
9318
9824
  if (!allDone)
9319
9825
  return "pending";
9320
- const allPass = checks.every((c) => c.conclusion === "SUCCESS" || c.conclusion === "NEUTRAL" || c.conclusion === "SKIPPED");
9826
+ const allPass = checks.every((c) => {
9827
+ if (c.__typename === "StatusContext")
9828
+ return c.state === "SUCCESS";
9829
+ return c.conclusion === "SUCCESS" || c.conclusion === "NEUTRAL" || c.conclusion === "SKIPPED";
9830
+ });
9321
9831
  return allPass ? "success" : "failed";
9322
9832
  }
9323
9833
  function parseRunId(detailsUrl) {
9834
+ if (!detailsUrl)
9835
+ return null;
9324
9836
  const match = detailsUrl.match(/\/actions\/runs\/(\d+)/);
9325
9837
  return match ? parseInt(match[1], 10) : null;
9326
9838
  }
9327
9839
  function deriveCheckStatus(check) {
9840
+ if (check.__typename === "StatusContext") {
9841
+ const s = check.state;
9842
+ if (s === "SUCCESS")
9843
+ return "success";
9844
+ if (s === "PENDING" || s === "EXPECTED")
9845
+ return "pending";
9846
+ return "failed";
9847
+ }
9328
9848
  if (check.status !== "COMPLETED")
9329
9849
  return "pending";
9330
9850
  const c = check.conclusion;
@@ -9337,12 +9857,16 @@ function deriveCheckStatus(check) {
9337
9857
  function mapChecks(checks) {
9338
9858
  if (!checks || checks.length === 0)
9339
9859
  return [];
9340
- return checks.map((c) => ({
9341
- name: c.name,
9342
- status: deriveCheckStatus(c),
9343
- url: c.detailsUrl,
9344
- runId: parseRunId(c.detailsUrl)
9345
- }));
9860
+ return checks.map((c) => {
9861
+ const name = c.__typename === "StatusContext" ? c.context : c.name;
9862
+ const url = c.__typename === "StatusContext" ? c.targetUrl : c.detailsUrl;
9863
+ return {
9864
+ name,
9865
+ status: deriveCheckStatus(c),
9866
+ url,
9867
+ runId: parseRunId(url)
9868
+ };
9869
+ });
9346
9870
  }
9347
9871
  function parseReviewComments(json) {
9348
9872
  const raw = JSON.parse(json);
@@ -9424,18 +9948,6 @@ async function fetchAllPrs(repoSlug, repoLabel, cwd) {
9424
9948
  return { ok: false, error: `failed to parse gh output for ${label}: ${err}` };
9425
9949
  }
9426
9950
  }
9427
- async function mapWithConcurrency(items, limit, fn) {
9428
- const results = new Array(items.length);
9429
- let next = 0;
9430
- async function worker() {
9431
- while (next < items.length) {
9432
- const idx = next++;
9433
- results[idx] = await fn(items[idx]);
9434
- }
9435
- }
9436
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
9437
- return results;
9438
- }
9439
9951
  async function fetchReviewComments(prNumber, repoSlug, cwd) {
9440
9952
  const repoFlag = repoSlug ? repoSlug : "{owner}/{repo}";
9441
9953
  const apiPath = `repos/${repoFlag}/pulls/${prNumber}/comments?per_page=100`;
@@ -9621,21 +10133,174 @@ async function syncPrStatus(getWorktreeGitDirs, linkedRepos, projectDir) {
9621
10133
  etagCache.delete(key);
9622
10134
  }
9623
10135
  }
9624
- function startPrMonitor(getWorktreeGitDirs, linkedRepos, projectDir, intervalMs = 20000, isActive) {
9625
- const run = () => {
10136
+ function startPrMonitor(getWorktreeGitDirs, linkedRepos, projectDir, intervalMs = 1e4, isActive, onAfterSync) {
10137
+ const run = async () => {
9626
10138
  if (isActive && !isActive()) {
9627
10139
  log.debug("[pr] skipping PR sync: no active clients");
9628
10140
  return;
9629
10141
  }
9630
- syncPrStatus(getWorktreeGitDirs, linkedRepos, projectDir).catch((err) => {
10142
+ await syncPrStatus(getWorktreeGitDirs, linkedRepos, projectDir).catch((err) => {
9631
10143
  log.error(`[pr] sync error: ${err}`);
9632
10144
  });
10145
+ if (onAfterSync) {
10146
+ await onAfterSync().catch((err) => {
10147
+ log.error(`[pr] after-sync hook error: ${err}`);
10148
+ });
10149
+ }
9633
10150
  };
9634
- run();
9635
- const timer = setInterval(run, intervalMs);
9636
- return () => {
9637
- clearInterval(timer);
10151
+ return startSerializedInterval(run, intervalMs);
10152
+ }
10153
+
10154
+ // backend/src/services/linear-auto-create-service.ts
10155
+ var POLL_INTERVAL_MS = 15000;
10156
+ var processedIssueIds = new Set;
10157
+ var AUTO_CREATE_LABEL = "webmux";
10158
+ function filterAutoCreateIssues(issues, existingBranches) {
10159
+ return issues.filter((issue) => {
10160
+ if (issue.state.name !== "Todo")
10161
+ return false;
10162
+ if (!issue.labels.some((l) => l.name.toLowerCase() === AUTO_CREATE_LABEL))
10163
+ return false;
10164
+ if (processedIssueIds.has(issue.id))
10165
+ return false;
10166
+ return !existingBranches.some((branch) => branchMatchesIssue(branch, issue.branchName));
10167
+ });
10168
+ }
10169
+ async function runAutoCreate(deps) {
10170
+ if (!deps.isActive()) {
10171
+ log.debug("[linear-auto-create] skipping: no active clients");
10172
+ return;
10173
+ }
10174
+ const result = await fetchAssignedIssues({ skipCache: true });
10175
+ if (!result.ok) {
10176
+ log.error(`[linear-auto-create] failed to fetch issues: ${result.error}`);
10177
+ return;
10178
+ }
10179
+ const projectRoot2 = deps.projectRoot;
10180
+ const existingBranches = deps.git.listWorktrees(projectRoot2).filter((entry) => !entry.bare && entry.branch !== null).map((entry) => entry.branch);
10181
+ const newIssues = filterAutoCreateIssues(result.data, existingBranches);
10182
+ if (newIssues.length === 0) {
10183
+ log.debug(`[linear-auto-create] no new labeled issues (${result.data.length} assigned, ${existingBranches.length} worktrees)`);
10184
+ return;
10185
+ }
10186
+ log.info(`[linear-auto-create] found ${newIssues.length} new issue(s) with "${AUTO_CREATE_LABEL}" label`);
10187
+ for (const issue of newIssues) {
10188
+ try {
10189
+ log.info(`[linear-auto-create] creating worktree for ${issue.identifier}: ${issue.title}`);
10190
+ await deps.lifecycleService.createWorktree({
10191
+ mode: "new",
10192
+ branch: issue.branchName,
10193
+ prompt: `${issue.title}
10194
+
10195
+ ${issue.description ?? ""}`.trim()
10196
+ });
10197
+ processedIssueIds.add(issue.id);
10198
+ log.info(`[linear-auto-create] created worktree for ${issue.identifier}`);
10199
+ } catch (err) {
10200
+ const msg = err instanceof Error ? err.message : String(err);
10201
+ log.error(`[linear-auto-create] failed to create worktree for ${issue.identifier}: ${msg}`);
10202
+ }
10203
+ }
10204
+ }
10205
+ function startLinearAutoCreateMonitor(deps) {
10206
+ log.info("[linear-auto-create] monitor started");
10207
+ return startSerializedInterval(() => runAutoCreate(deps), POLL_INTERVAL_MS);
10208
+ }
10209
+ function resetProcessedIssues() {
10210
+ processedIssueIds.clear();
10211
+ }
10212
+
10213
+ // backend/src/services/auto-remove-service.ts
10214
+ async function runAutoRemove(deps) {
10215
+ const worktrees = deps.git.listWorktrees(deps.projectRoot).filter((e) => !e.bare && e.branch !== null && e.path !== deps.projectRoot);
10216
+ for (const entry of worktrees) {
10217
+ const branch = entry.branch;
10218
+ if (deps.isRemoving(branch))
10219
+ continue;
10220
+ const prs = await readWorktreePrs(deps.git.resolveWorktreeGitDir(entry.path));
10221
+ if (prs.length === 0)
10222
+ continue;
10223
+ if (!prs.every((pr) => pr.state === "merged"))
10224
+ continue;
10225
+ if (deps.git.readWorktreeStatus(entry.path).dirty) {
10226
+ log.info(`[auto-remove] skipping dirty worktree: ${branch}`);
10227
+ continue;
10228
+ }
10229
+ deps.markRemoving(branch);
10230
+ try {
10231
+ log.info(`[auto-remove] removing merged worktree: ${branch}`);
10232
+ await deps.lifecycleService.removeWorktree(branch);
10233
+ deps.notifications.notify({
10234
+ branch,
10235
+ type: "worktree_auto_removed",
10236
+ message: `Worktree auto-removed after merge: ${branch}`
10237
+ });
10238
+ log.info(`[auto-remove] removed worktree: ${branch}`);
10239
+ } catch (err) {
10240
+ const msg = err instanceof Error ? err.message : String(err);
10241
+ log.error(`[auto-remove] failed to remove worktree ${branch}: ${msg}`);
10242
+ } finally {
10243
+ deps.unmarkRemoving(branch);
10244
+ }
10245
+ }
10246
+ }
10247
+
10248
+ // backend/src/services/auto-pull-service.ts
10249
+ function pullMainBranch(deps) {
10250
+ const { git, projectRoot: projectRoot2, mainBranch } = deps;
10251
+ const beforeCommit = git.readWorktreeStatus(projectRoot2).currentCommit;
10252
+ const fetchResult = git.fetchBranch(projectRoot2, "origin", mainBranch);
10253
+ if (!fetchResult.ok) {
10254
+ return { status: "fetch_failed", error: fetchResult.stderr };
10255
+ }
10256
+ const mergeResult = git.fastForwardMerge(projectRoot2, `origin/${mainBranch}`);
10257
+ if (!mergeResult.ok) {
10258
+ return { status: "merge_failed", error: mergeResult.stderr };
10259
+ }
10260
+ const afterStatus = git.readWorktreeStatus(projectRoot2);
10261
+ const afterCommit = afterStatus.currentCommit;
10262
+ if (beforeCommit === afterCommit) {
10263
+ return { status: "already_up_to_date" };
10264
+ }
10265
+ return { status: "updated", from: beforeCommit ?? "unknown", to: afterCommit ?? "unknown" };
10266
+ }
10267
+ function forcePullMainBranch(deps) {
10268
+ const { git, projectRoot: projectRoot2, mainBranch } = deps;
10269
+ const beforeCommit = git.readWorktreeStatus(projectRoot2).currentCommit;
10270
+ const fetchResult = git.fetchBranch(projectRoot2, "origin", mainBranch);
10271
+ if (!fetchResult.ok) {
10272
+ return { status: "fetch_failed", error: fetchResult.stderr };
10273
+ }
10274
+ const resetResult = git.hardReset(projectRoot2, `origin/${mainBranch}`);
10275
+ if (!resetResult.ok) {
10276
+ return { status: "merge_failed", error: resetResult.stderr };
10277
+ }
10278
+ const afterCommit = git.readWorktreeStatus(projectRoot2).currentCommit;
10279
+ if (beforeCommit === afterCommit) {
10280
+ return { status: "already_up_to_date" };
10281
+ }
10282
+ return { status: "updated", from: beforeCommit ?? "unknown", to: afterCommit ?? "unknown" };
10283
+ }
10284
+ function startAutoPullMonitor(deps, intervalMs) {
10285
+ log.info(`[auto-pull] monitor started (interval: ${intervalMs}ms)`);
10286
+ const run = async () => {
10287
+ const result = pullMainBranch(deps);
10288
+ switch (result.status) {
10289
+ case "updated":
10290
+ log.info(`[auto-pull] updated ${deps.mainBranch}: ${result.from.slice(0, 8)} \u2192 ${result.to.slice(0, 8)}`);
10291
+ break;
10292
+ case "already_up_to_date":
10293
+ log.debug("[auto-pull] already up to date");
10294
+ break;
10295
+ case "fetch_failed":
10296
+ log.warn(`[auto-pull] fetch failed: ${result.error}`);
10297
+ break;
10298
+ case "merge_failed":
10299
+ log.warn(`[auto-pull] merge failed (ff-only): ${result.error}`);
10300
+ break;
10301
+ }
9638
10302
  };
10303
+ return startSerializedInterval(run, intervalMs);
9639
10304
  }
9640
10305
 
9641
10306
  // backend/src/services/snapshot-service.ts
@@ -9672,6 +10337,7 @@ function mapCreationSnapshot(creating) {
9672
10337
  function mapWorktreeSnapshot(state, now, creating, findLinearIssue) {
9673
10338
  return {
9674
10339
  branch: state.branch,
10340
+ ...state.baseBranch ? { baseBranch: state.baseBranch } : {},
9675
10341
  path: state.path,
9676
10342
  dir: state.path,
9677
10343
  profile: state.profile,
@@ -9691,6 +10357,7 @@ function mapWorktreeSnapshot(state, now, creating, findLinearIssue) {
9691
10357
  function mapCreatingWorktreeSnapshot(creating, findLinearIssue) {
9692
10358
  return {
9693
10359
  branch: creating.branch,
10360
+ ...creating.baseBranch ? { baseBranch: creating.baseBranch } : {},
9694
10361
  path: creating.path,
9695
10362
  dir: creating.path,
9696
10363
  profile: creating.profile,
@@ -10058,8 +10725,8 @@ class BunLifecycleHookRunner {
10058
10725
  }
10059
10726
  async run(input) {
10060
10727
  const cmd = await this.buildCommand(input.cwd, input.command);
10061
- console.debug(`[hook-runner] Spawning: ${cmd.join(" ")} cwd=${input.cwd}`);
10062
- console.debug(`[hook-runner] Env keys: ${Object.keys(input.env).join(", ")}`);
10728
+ log.debug(`[hook-runner] Spawning: ${cmd.join(" ")} cwd=${input.cwd}`);
10729
+ log.debug(`[hook-runner] envKeys=${Object.keys(input.env).length}`);
10063
10730
  const proc = Bun.spawn(cmd, {
10064
10731
  cwd: input.cwd,
10065
10732
  env: {
@@ -10074,11 +10741,11 @@ class BunLifecycleHookRunner {
10074
10741
  new Response(proc.stdout).text(),
10075
10742
  new Response(proc.stderr).text()
10076
10743
  ]);
10077
- console.debug(`[hook-runner] ${input.name} exitCode=${exitCode}`);
10744
+ log.debug(`[hook-runner] ${input.name} exitCode=${exitCode}`);
10078
10745
  if (stdout.trim())
10079
- console.debug(`[hook-runner] stdout: ${stdout.trim()}`);
10746
+ log.debug(`[hook-runner] stdout: ${stdout.trim()}`);
10080
10747
  if (stderr.trim())
10081
- console.debug(`[hook-runner] stderr: ${stderr.trim()}`);
10748
+ log.debug(`[hook-runner] stderr: ${stderr.trim()}`);
10082
10749
  if (exitCode !== 0) {
10083
10750
  throw new Error(buildErrorMessage(input.name, exitCode, stdout, stderr));
10084
10751
  }
@@ -10094,7 +10761,7 @@ class BunPortProbe {
10094
10761
  this.hostnames = hostnames;
10095
10762
  }
10096
10763
  isListening(port) {
10097
- return new Promise((resolve5) => {
10764
+ return new Promise((resolve6) => {
10098
10765
  let settled = false;
10099
10766
  let pending = this.hostnames.length;
10100
10767
  const settle = (result) => {
@@ -10103,20 +10770,20 @@ class BunPortProbe {
10103
10770
  if (result) {
10104
10771
  settled = true;
10105
10772
  clearTimeout(timer);
10106
- resolve5(true);
10773
+ resolve6(true);
10107
10774
  return;
10108
10775
  }
10109
10776
  pending--;
10110
10777
  if (pending === 0) {
10111
10778
  settled = true;
10112
10779
  clearTimeout(timer);
10113
- resolve5(false);
10780
+ resolve6(false);
10114
10781
  }
10115
10782
  };
10116
10783
  const timer = setTimeout(() => {
10117
10784
  if (!settled) {
10118
10785
  settled = true;
10119
- resolve5(false);
10786
+ resolve6(false);
10120
10787
  }
10121
10788
  }, this.timeoutMs);
10122
10789
  for (const hostname of this.hostnames) {
@@ -10253,33 +10920,14 @@ class AutoNameService {
10253
10920
  }
10254
10921
 
10255
10922
  // backend/src/services/notification-service.ts
10256
- function buildNotification(event, id, timestamp) {
10923
+ function eventToNotificationInput(event) {
10257
10924
  switch (event.type) {
10258
10925
  case "agent_stopped":
10259
- return {
10260
- id,
10261
- branch: event.branch,
10262
- type: "agent_stopped",
10263
- message: `Agent stopped on ${event.branch}`,
10264
- timestamp
10265
- };
10926
+ return { branch: event.branch, type: "agent_stopped", message: `Agent stopped on ${event.branch}` };
10266
10927
  case "pr_opened":
10267
- return {
10268
- id,
10269
- branch: event.branch,
10270
- type: "pr_opened",
10271
- message: `PR opened on ${event.branch}`,
10272
- url: event.url,
10273
- timestamp
10274
- };
10928
+ return { branch: event.branch, type: "pr_opened", message: `PR opened on ${event.branch}`, url: event.url };
10275
10929
  case "runtime_error":
10276
- return {
10277
- id,
10278
- branch: event.branch,
10279
- type: "runtime_error",
10280
- message: `Runtime error on ${event.branch}: ${event.message}`,
10281
- timestamp
10282
- };
10930
+ return { branch: event.branch, type: "runtime_error", message: `Runtime error on ${event.branch}: ${event.message}` };
10283
10931
  default:
10284
10932
  return null;
10285
10933
  }
@@ -10304,10 +10952,15 @@ class NotificationService {
10304
10952
  this.broadcast("dismiss", { id });
10305
10953
  return true;
10306
10954
  }
10307
- recordEvent(event, now = () => new Date) {
10308
- const notification = buildNotification(event, this.nextId, now().getTime());
10309
- if (!notification)
10310
- return null;
10955
+ notify(input) {
10956
+ const notification = {
10957
+ id: this.nextId,
10958
+ branch: input.branch,
10959
+ type: input.type,
10960
+ message: input.message,
10961
+ ...input.url ? { url: input.url } : {},
10962
+ timestamp: Date.now()
10963
+ };
10311
10964
  this.nextId += 1;
10312
10965
  this.notifications.push(notification);
10313
10966
  while (this.notifications.length > this.maxItems) {
@@ -10316,6 +10969,12 @@ class NotificationService {
10316
10969
  this.broadcast("notification", notification);
10317
10970
  return notification;
10318
10971
  }
10972
+ recordEvent(event) {
10973
+ const input = eventToNotificationInput(event);
10974
+ if (!input)
10975
+ return null;
10976
+ return this.notify(input);
10977
+ }
10319
10978
  stream() {
10320
10979
  let controllerRef = null;
10321
10980
  const stream = new ReadableStream({
@@ -10365,6 +11024,7 @@ function makeDefaultState(input) {
10365
11024
  return {
10366
11025
  worktreeId: input.worktreeId,
10367
11026
  branch: input.branch,
11027
+ baseBranch: input.baseBranch ?? null,
10368
11028
  path: input.path,
10369
11029
  profile: input.profile ?? null,
10370
11030
  agentName: input.agentName ?? null,
@@ -10409,6 +11069,8 @@ class ProjectRuntime {
10409
11069
  this.reindexBranch(existing.branch, input.branch, input.worktreeId);
10410
11070
  existing.path = input.path;
10411
11071
  existing.branch = input.branch;
11072
+ if (input.baseBranch !== undefined)
11073
+ existing.baseBranch = input.baseBranch;
10412
11074
  existing.profile = input.profile ?? existing.profile;
10413
11075
  existing.agentName = input.agentName ?? existing.agentName;
10414
11076
  if (input.runtime)
@@ -10521,9 +11183,9 @@ class ProjectRuntime {
10521
11183
  }
10522
11184
 
10523
11185
  // backend/src/services/reconciliation-service.ts
10524
- import { basename as basename2, resolve as resolve5 } from "path";
11186
+ import { basename as basename2, resolve as resolve6 } from "path";
10525
11187
  function makeUnmanagedWorktreeId(path) {
10526
- return `unmanaged:${resolve5(path)}`;
11188
+ return `unmanaged:${resolve6(path)}`;
10527
11189
  }
10528
11190
  function isValidPort2(port) {
10529
11191
  return port !== null && Number.isInteger(port) && port >= 1 && port <= 65535;
@@ -10562,11 +11224,34 @@ function resolveBranch(entry, metaBranch) {
10562
11224
 
10563
11225
  class ReconciliationService {
10564
11226
  deps;
10565
- constructor(deps) {
11227
+ freshnessMs;
11228
+ now;
11229
+ concurrency;
11230
+ inFlight = null;
11231
+ lastReconciledAt = 0;
11232
+ constructor(deps, options = {}) {
10566
11233
  this.deps = deps;
11234
+ this.freshnessMs = options.freshnessMs ?? 500;
11235
+ this.now = options.now ?? Date.now;
11236
+ this.concurrency = options.concurrency ?? 4;
10567
11237
  }
10568
- async reconcile(repoRoot) {
10569
- const normalizedRepoRoot = resolve5(repoRoot);
11238
+ async reconcile(repoRoot, options = {}) {
11239
+ if (this.inFlight) {
11240
+ return await this.inFlight;
11241
+ }
11242
+ if (!options.force && this.now() - this.lastReconciledAt < this.freshnessMs) {
11243
+ return;
11244
+ }
11245
+ const normalizedRepoRoot = resolve6(repoRoot);
11246
+ const reconcilePromise = this.runReconcile(normalizedRepoRoot).then(() => {
11247
+ this.lastReconciledAt = this.now();
11248
+ });
11249
+ this.inFlight = reconcilePromise.finally(() => {
11250
+ this.inFlight = null;
11251
+ });
11252
+ return await this.inFlight;
11253
+ }
11254
+ async runReconcile(normalizedRepoRoot) {
10570
11255
  const worktrees = this.deps.git.listWorktrees(normalizedRepoRoot);
10571
11256
  const sessionName = buildProjectSessionName(normalizedRepoRoot);
10572
11257
  let windows = [];
@@ -10576,40 +11261,33 @@ class ReconciliationService {
10576
11261
  windows = [];
10577
11262
  }
10578
11263
  const seenWorktreeIds = new Set;
10579
- for (const entry of worktrees) {
10580
- if (entry.bare)
10581
- continue;
10582
- if (resolve5(entry.path) === normalizedRepoRoot)
10583
- continue;
11264
+ const candidateEntries = worktrees.filter((entry) => !entry.bare && resolve6(entry.path) !== normalizedRepoRoot);
11265
+ const reconciledStates = await mapWithConcurrency(candidateEntries, this.concurrency, async (entry) => {
10584
11266
  const gitDir = this.deps.git.resolveWorktreeGitDir(entry.path);
10585
11267
  const meta = await readWorktreeMeta(gitDir);
10586
11268
  const branch = resolveBranch(entry, meta?.branch ?? null);
10587
11269
  const worktreeId = meta?.worktreeId ?? makeUnmanagedWorktreeId(entry.path);
10588
- seenWorktreeIds.add(worktreeId);
10589
- this.deps.runtime.upsertWorktree({
11270
+ const gitStatus = this.deps.git.readWorktreeStatus(entry.path);
11271
+ const window = findWindow(windows, sessionName, branch);
11272
+ return {
10590
11273
  worktreeId,
10591
11274
  branch,
11275
+ baseBranch: meta?.baseBranch ?? null,
10592
11276
  path: entry.path,
10593
11277
  profile: meta?.profile ?? null,
10594
11278
  agentName: meta?.agent ?? null,
10595
- runtime: meta?.runtime ?? "host"
10596
- });
10597
- const gitStatus = this.deps.git.readWorktreeStatus(entry.path);
10598
- this.deps.runtime.setGitState(worktreeId, {
10599
- exists: true,
10600
- branch,
10601
- dirty: gitStatus.dirty,
10602
- aheadCount: gitStatus.aheadCount,
10603
- currentCommit: gitStatus.currentCommit
10604
- });
10605
- const window = findWindow(windows, sessionName, branch);
10606
- this.deps.runtime.setSessionState(worktreeId, {
10607
- exists: window !== null,
10608
- sessionName: window?.sessionName ?? null,
10609
- paneCount: window?.paneCount ?? 0
10610
- });
10611
- if (meta) {
10612
- this.deps.runtime.setServices(worktreeId, await buildServiceStates(this.deps, {
11279
+ runtime: meta?.runtime ?? "host",
11280
+ git: {
11281
+ dirty: gitStatus.dirty,
11282
+ aheadCount: gitStatus.aheadCount,
11283
+ currentCommit: gitStatus.currentCommit
11284
+ },
11285
+ session: {
11286
+ exists: window !== null,
11287
+ sessionName: window?.sessionName ?? null,
11288
+ paneCount: window?.paneCount ?? 0
11289
+ },
11290
+ services: meta ? await buildServiceStates(this.deps, {
10613
11291
  allocatedPorts: meta.allocatedPorts,
10614
11292
  startupEnvValues: meta.startupEnvValues,
10615
11293
  worktreeId: meta.worktreeId,
@@ -10617,11 +11295,35 @@ class ReconciliationService {
10617
11295
  profile: meta.profile,
10618
11296
  agent: meta.agent,
10619
11297
  runtime: meta.runtime
10620
- }));
10621
- } else {
10622
- this.deps.runtime.setServices(worktreeId, []);
10623
- }
10624
- this.deps.runtime.setPrs(worktreeId, await readWorktreePrs(gitDir));
11298
+ }) : [],
11299
+ prs: await readWorktreePrs(gitDir)
11300
+ };
11301
+ });
11302
+ for (const state of reconciledStates) {
11303
+ seenWorktreeIds.add(state.worktreeId);
11304
+ this.deps.runtime.upsertWorktree({
11305
+ worktreeId: state.worktreeId,
11306
+ branch: state.branch,
11307
+ baseBranch: state.baseBranch,
11308
+ path: state.path,
11309
+ profile: state.profile,
11310
+ agentName: state.agentName,
11311
+ runtime: state.runtime
11312
+ });
11313
+ this.deps.runtime.setGitState(state.worktreeId, {
11314
+ exists: true,
11315
+ branch: state.branch,
11316
+ dirty: state.git.dirty,
11317
+ aheadCount: state.git.aheadCount,
11318
+ currentCommit: state.git.currentCommit
11319
+ });
11320
+ this.deps.runtime.setSessionState(state.worktreeId, {
11321
+ exists: state.session.exists,
11322
+ sessionName: state.session.sessionName,
11323
+ paneCount: state.session.paneCount
11324
+ });
11325
+ this.deps.runtime.setServices(state.worktreeId, state.services);
11326
+ this.deps.runtime.setPrs(state.worktreeId, state.prs);
10625
11327
  }
10626
11328
  for (const state of this.deps.runtime.listWorktrees()) {
10627
11329
  if (!seenWorktreeIds.has(state.worktreeId)) {
@@ -10637,6 +11339,7 @@ class WorktreeCreationTracker {
10637
11339
  set(progress) {
10638
11340
  const next = {
10639
11341
  branch: progress.branch,
11342
+ ...progress.baseBranch ? { baseBranch: progress.baseBranch } : {},
10640
11343
  path: progress.path,
10641
11344
  profile: progress.profile,
10642
11345
  agentName: progress.agent,
@@ -10729,6 +11432,34 @@ var runtimeNotifications = runtime.runtimeNotifications;
10729
11432
  var reconciliationService = runtime.reconciliationService;
10730
11433
  var removingBranches = new Set;
10731
11434
  var lifecycleService = runtime.lifecycleService;
11435
+ var linearAutoCreateEnabled = config.integrations.linear.autoCreateWorktrees;
11436
+ var stopLinearAutoCreate = null;
11437
+ var autoRemoveOnMergeEnabled = config.integrations.github.autoRemoveOnMerge;
11438
+ function startLinearAutoCreate() {
11439
+ if (stopLinearAutoCreate)
11440
+ return;
11441
+ stopLinearAutoCreate = startLinearAutoCreateMonitor({
11442
+ lifecycleService,
11443
+ git,
11444
+ projectRoot: PROJECT_DIR,
11445
+ isActive: hasRecentDashboardActivity
11446
+ });
11447
+ }
11448
+ function stopLinearAutoCreateMonitor() {
11449
+ if (stopLinearAutoCreate) {
11450
+ stopLinearAutoCreate();
11451
+ stopLinearAutoCreate = null;
11452
+ }
11453
+ }
11454
+ var autoRemoveDeps = {
11455
+ lifecycleService,
11456
+ git,
11457
+ projectRoot: PROJECT_DIR,
11458
+ notifications: runtimeNotifications,
11459
+ isRemoving: (branch) => removingBranches.has(branch),
11460
+ markRemoving: (branch) => removingBranches.add(branch),
11461
+ unmarkRemoving: (branch) => removingBranches.delete(branch)
11462
+ };
10732
11463
  function getFrontendConfig() {
10733
11464
  const defaultProfileName = getDefaultProfileName(config);
10734
11465
  const orderedProfileEntries = Object.entries(config.profiles).sort(([left], [right]) => {
@@ -10747,11 +11478,16 @@ function getFrontendConfig() {
10747
11478
  })),
10748
11479
  defaultProfileName,
10749
11480
  autoName: config.autoName !== null,
11481
+ linearCreateTicketOption: config.integrations.linear.enabled && config.integrations.linear.createTicketOption,
10750
11482
  startupEnvs: config.startupEnvs,
10751
11483
  linkedRepos: config.integrations.github.linkedRepos.map((lr) => ({
10752
11484
  alias: lr.alias,
10753
- ...lr.dir ? { dir: resolve6(PROJECT_DIR, lr.dir) } : {}
10754
- }))
11485
+ ...lr.dir ? { dir: resolve7(PROJECT_DIR, lr.dir) } : {}
11486
+ })),
11487
+ linearAutoCreateWorktrees: linearAutoCreateEnabled,
11488
+ autoRemoveOnMerge: autoRemoveOnMergeEnabled,
11489
+ projectDir: PROJECT_DIR,
11490
+ mainBranch: config.workspace.mainBranch
10755
11491
  };
10756
11492
  }
10757
11493
  function parseWsMessage(raw) {
@@ -10829,8 +11565,11 @@ async function withRemovingBranch(branch, fn) {
10829
11565
  }
10830
11566
  async function resolveTerminalWorktree(branch) {
10831
11567
  ensureBranchNotBusy(branch);
10832
- await reconciliationService.reconcile(PROJECT_DIR);
10833
- const state = projectRuntime.getWorktreeByBranch(branch);
11568
+ let state = projectRuntime.getWorktreeByBranch(branch);
11569
+ if (!state || !state.session.exists || !state.session.sessionName) {
11570
+ await reconciliationService.reconcile(PROJECT_DIR);
11571
+ state = projectRuntime.getWorktreeByBranch(branch);
11572
+ }
10834
11573
  if (!state) {
10835
11574
  throw new Error(`Worktree not found: ${branch}`);
10836
11575
  }
@@ -10845,9 +11584,24 @@ async function resolveTerminalWorktree(branch) {
10845
11584
  }
10846
11585
  };
10847
11586
  }
10848
- function getAttachedWorktreeId(ws) {
10849
- if (ws.data.attached && ws.data.worktreeId) {
10850
- return ws.data.worktreeId;
11587
+ async function apiGetNativeTerminalLaunch(branch) {
11588
+ touchDashboardActivity();
11589
+ ensureBranchNotBusy(branch);
11590
+ await reconciliationService.reconcile(PROJECT_DIR);
11591
+ const launch = buildNativeTerminalLaunch({
11592
+ branch,
11593
+ state: projectRuntime.getWorktreeByBranch(branch),
11594
+ tmuxCommand: buildNativeTerminalTmuxCommand(Bun.env),
11595
+ sessionPrefix: `wm-native-${PORT}-`
11596
+ });
11597
+ if (!launch.ok) {
11598
+ return errorResponse(launch.message, launch.reason === "not_found" ? 404 : 409);
11599
+ }
11600
+ return jsonResponse(launch.data);
11601
+ }
11602
+ function getAttachedSessionId(ws) {
11603
+ if (ws.data.attached && ws.data.attachId) {
11604
+ return ws.data.attachId;
10851
11605
  }
10852
11606
  sendWs(ws, { type: "error", message: "Terminal not attached" });
10853
11607
  return null;
@@ -10859,9 +11613,9 @@ async function hasValidControlToken(req) {
10859
11613
  }
10860
11614
  async function getWorktreeGitDirs() {
10861
11615
  const gitDirs = new Map;
10862
- const projectRoot2 = resolve6(PROJECT_DIR);
11616
+ const projectRoot2 = resolve7(PROJECT_DIR);
10863
11617
  for (const entry of git.listWorktrees(projectRoot2)) {
10864
- if (entry.bare || resolve6(entry.path) === projectRoot2 || !entry.branch)
11618
+ if (entry.bare || resolve7(entry.path) === projectRoot2 || !entry.branch)
10865
11619
  continue;
10866
11620
  gitDirs.set(entry.branch, git.resolveWorktreeGitDir(entry.path));
10867
11621
  }
@@ -10881,7 +11635,8 @@ function makeCallbacks(ws) {
10881
11635
  }
10882
11636
  async function apiGetProject() {
10883
11637
  touchDashboardActivity();
10884
- const linearIssuesPromise = config.integrations.linear.enabled ? fetchAssignedIssues() : Promise.resolve({ ok: true, data: [] });
11638
+ const linearApiKey = Bun.env.LINEAR_API_KEY;
11639
+ const linearIssuesPromise = config.integrations.linear.enabled && linearApiKey?.trim() ? fetchAssignedIssues() : Promise.resolve({ ok: true, data: [] });
10885
11640
  const [, linearResult] = await Promise.all([
10886
11641
  reconciliationService.reconcile(PROJECT_DIR),
10887
11642
  linearIssuesPromise
@@ -10916,15 +11671,24 @@ async function apiRuntimeEvent(req) {
10916
11671
  const event = parseRuntimeEvent(raw);
10917
11672
  if (!event)
10918
11673
  return errorResponse("Invalid runtime event body", 400);
10919
- await reconciliationService.reconcile(PROJECT_DIR);
10920
11674
  try {
10921
11675
  projectRuntime.applyEvent(event);
10922
11676
  } catch (error) {
10923
11677
  const message = error instanceof Error ? error.message : String(error);
10924
11678
  if (message.includes("Unknown worktree id")) {
10925
- return errorResponse(message, 404);
11679
+ await reconciliationService.reconcile(PROJECT_DIR);
11680
+ try {
11681
+ projectRuntime.applyEvent(event);
11682
+ } catch (retryError) {
11683
+ const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
11684
+ if (retryMessage.includes("Unknown worktree id")) {
11685
+ return errorResponse(retryMessage, 404);
11686
+ }
11687
+ throw retryError;
11688
+ }
11689
+ } else {
11690
+ throw error;
10926
11691
  }
10927
- throw error;
10928
11692
  }
10929
11693
  const notification = runtimeNotifications.recordEvent(event);
10930
11694
  return jsonResponse({
@@ -10937,6 +11701,11 @@ async function apiListBranches() {
10937
11701
  branches: lifecycleService.listAvailableBranches()
10938
11702
  });
10939
11703
  }
11704
+ async function apiListBaseBranches() {
11705
+ return jsonResponse({
11706
+ branches: lifecycleService.listBaseBranches()
11707
+ });
11708
+ }
10940
11709
  async function apiCreateWorktree(req) {
10941
11710
  const raw = await req.json();
10942
11711
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
@@ -10954,21 +11723,67 @@ async function apiCreateWorktree(req) {
10954
11723
  if (Object.keys(parsed).length > 0)
10955
11724
  envOverrides = parsed;
10956
11725
  }
10957
- const branch = typeof body.branch === "string" ? body.branch : undefined;
10958
- const prompt = typeof body.prompt === "string" ? body.prompt : undefined;
11726
+ const branch = typeof body.branch === "string" && body.branch.trim() ? body.branch.trim() : undefined;
11727
+ const baseBranch = typeof body.baseBranch === "string" && body.baseBranch.trim() ? body.baseBranch.trim() : undefined;
11728
+ const prompt = typeof body.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : undefined;
10959
11729
  const profile = typeof body.profile === "string" ? body.profile : undefined;
10960
11730
  const agent = body.agent === "claude" || body.agent === "codex" ? body.agent : undefined;
10961
- const mode = body.mode;
10962
- if (mode !== undefined && mode !== "new" && mode !== "existing") {
11731
+ const createLinearTicket = body.createLinearTicket === true;
11732
+ const linearTitle = typeof body.linearTitle === "string" && body.linearTitle.trim() ? body.linearTitle.trim() : undefined;
11733
+ const mode = body.mode === "new" || body.mode === "existing" ? body.mode : undefined;
11734
+ if (body.mode !== undefined && body.mode !== "new" && body.mode !== "existing") {
10963
11735
  return errorResponse("Invalid worktree create mode", 400);
10964
11736
  }
10965
- if (branch) {
10966
- ensureBranchNotCreating(branch);
11737
+ if (baseBranch && !isValidBranchName(baseBranch)) {
11738
+ return errorResponse("Invalid base branch name", 400);
11739
+ }
11740
+ if (createLinearTicket && mode === "existing") {
11741
+ return errorResponse("Linear ticket creation is only supported for new branches", 400);
11742
+ }
11743
+ if (baseBranch && mode === "existing") {
11744
+ return errorResponse("Base branch is only supported for new branches", 400);
11745
+ }
11746
+ if (createLinearTicket && !config.integrations.linear.enabled) {
11747
+ return errorResponse("Linear integration is disabled", 400);
11748
+ }
11749
+ if (createLinearTicket && !config.integrations.linear.createTicketOption) {
11750
+ return errorResponse("Linear ticket creation is not enabled for this project", 400);
10967
11751
  }
10968
- log.info(`[worktree:add] mode=${mode ?? "new"}${branch ? ` branch=${branch}` : ""}${profile ? ` profile=${profile}` : ""}${agent ? ` agent=${agent}` : ""}${prompt ? ` prompt="${prompt.slice(0, 80)}"` : ""}`);
11752
+ if (createLinearTicket && !prompt) {
11753
+ return errorResponse("Prompt is required when creating a Linear ticket", 400);
11754
+ }
11755
+ let resolvedBranch = branch;
11756
+ if (createLinearTicket) {
11757
+ const title = deriveLinearIssueTitle(linearTitle, prompt);
11758
+ if (!title) {
11759
+ return errorResponse("Linear ticket title could not be derived from the prompt", 400);
11760
+ }
11761
+ const teamId = config.integrations.linear.teamId;
11762
+ if (!teamId) {
11763
+ return errorResponse("Linear teamId is not configured", 503);
11764
+ }
11765
+ const linearResult = await createLinearIssue({
11766
+ title,
11767
+ description: prompt ?? "",
11768
+ teamId
11769
+ });
11770
+ if (!linearResult.ok) {
11771
+ return errorResponse(linearResult.error, 502);
11772
+ }
11773
+ resolvedBranch = linearResult.data.branchName;
11774
+ ensureBranchNotCreating(resolvedBranch);
11775
+ log.info(`[linear] created ticket ${linearResult.data.identifier} branch=${linearResult.data.branchName} title="${linearResult.data.title.slice(0, 80)}"`);
11776
+ } else if (resolvedBranch) {
11777
+ ensureBranchNotCreating(resolvedBranch);
11778
+ }
11779
+ if (resolvedBranch && baseBranch && resolvedBranch === baseBranch) {
11780
+ return errorResponse("Base branch must differ from branch name", 400);
11781
+ }
11782
+ log.info(`[worktree:add] mode=${mode ?? "new"}${resolvedBranch ? ` branch=${resolvedBranch}` : ""}${baseBranch ? ` base=${baseBranch}` : ""}${profile ? ` profile=${profile}` : ""}${agent ? ` agent=${agent}` : ""}${createLinearTicket ? " linearTicket=true" : ""}${prompt ? ` prompt="${prompt.slice(0, 80)}"` : ""}`);
10969
11783
  const result = await lifecycleService.createWorktree({
10970
11784
  mode,
10971
- branch,
11785
+ branch: resolvedBranch,
11786
+ baseBranch,
10972
11787
  prompt,
10973
11788
  profile,
10974
11789
  agent,
@@ -11024,8 +11839,58 @@ async function apiMergeWorktree(name) {
11024
11839
  log.debug(`[worktree:merge] done name=${name}`);
11025
11840
  return jsonResponse({ ok: true });
11026
11841
  }
11842
+ async function apiSetLinearAutoCreate(req) {
11843
+ const raw = await req.json();
11844
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
11845
+ return errorResponse("Invalid request body", 400);
11846
+ }
11847
+ const body = raw;
11848
+ if (typeof body.enabled !== "boolean") {
11849
+ return errorResponse("Missing boolean 'enabled' field", 400);
11850
+ }
11851
+ linearAutoCreateEnabled = body.enabled;
11852
+ if (linearAutoCreateEnabled) {
11853
+ resetProcessedIssues();
11854
+ startLinearAutoCreate();
11855
+ log.info("[config] Linear auto-create worktrees enabled");
11856
+ } else {
11857
+ stopLinearAutoCreateMonitor();
11858
+ log.info("[config] Linear auto-create worktrees disabled");
11859
+ }
11860
+ await persistLocalLinearConfig(PROJECT_DIR, { autoCreateWorktrees: linearAutoCreateEnabled });
11861
+ return jsonResponse({ ok: true, enabled: linearAutoCreateEnabled });
11862
+ }
11863
+ async function apiSetAutoRemoveOnMerge(req) {
11864
+ const raw = await req.json();
11865
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
11866
+ return errorResponse("Invalid request body", 400);
11867
+ }
11868
+ const body = raw;
11869
+ if (typeof body.enabled !== "boolean") {
11870
+ return errorResponse("Missing boolean 'enabled' field", 400);
11871
+ }
11872
+ autoRemoveOnMergeEnabled = body.enabled;
11873
+ log.info(`[config] Auto-remove on merge ${autoRemoveOnMergeEnabled ? "enabled" : "disabled"}`);
11874
+ await persistLocalGitHubConfig(PROJECT_DIR, { autoRemoveOnMerge: autoRemoveOnMergeEnabled });
11875
+ return jsonResponse({ ok: true, enabled: autoRemoveOnMergeEnabled });
11876
+ }
11877
+ async function apiPullMain(req) {
11878
+ const raw = await req.json().catch(() => ({}));
11879
+ const body = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
11880
+ const force = body.force === true;
11881
+ const deps = { git, projectRoot: PROJECT_DIR, mainBranch: config.workspace.mainBranch };
11882
+ const result = force ? forcePullMainBranch(deps) : pullMainBranch(deps);
11883
+ log.info(`[pull-main] ${force ? "force " : ""}pull: ${result.status}`);
11884
+ return jsonResponse(result);
11885
+ }
11027
11886
  async function apiGetLinearIssues() {
11028
- const result = await fetchAssignedIssues();
11887
+ const apiKey = Bun.env.LINEAR_API_KEY;
11888
+ const fetchResult = config.integrations.linear.enabled && apiKey?.trim() ? await fetchAssignedIssues() : undefined;
11889
+ const result = buildLinearIssuesResponse({
11890
+ integrationEnabled: config.integrations.linear.enabled,
11891
+ apiKey,
11892
+ fetchResult
11893
+ });
11029
11894
  if (!result.ok)
11030
11895
  return errorResponse(result.error, 502);
11031
11896
  return jsonResponse(result.data);
@@ -11098,7 +11963,7 @@ async function apiUploadFiles(name, req) {
11098
11963
  }
11099
11964
  const safeName = `${Date.now()}_${sanitizeFilename(entry.name)}`;
11100
11965
  const destPath = join6(uploadDir, safeName);
11101
- if (!resolve6(destPath).startsWith(uploadDir + "/")) {
11966
+ if (!resolve7(destPath).startsWith(uploadDir + "/")) {
11102
11967
  return errorResponse("Invalid filename", 400);
11103
11968
  }
11104
11969
  await Bun.write(destPath, entry);
@@ -11113,7 +11978,7 @@ Bun.serve({
11113
11978
  routes: {
11114
11979
  "/ws/:worktree": (req, server) => {
11115
11980
  const branch = decodeURIComponent(req.params.worktree);
11116
- return server.upgrade(req, { data: { branch, worktreeId: null, attached: false } }) ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
11981
+ return server.upgrade(req, { data: { branch, worktreeId: null, attachId: null, attached: false } }) ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
11117
11982
  },
11118
11983
  "/api/config": {
11119
11984
  GET: () => jsonResponse(getFrontendConfig())
@@ -11121,6 +11986,9 @@ Bun.serve({
11121
11986
  "/api/branches": {
11122
11987
  GET: () => catching("GET /api/branches", () => apiListBranches())
11123
11988
  },
11989
+ "/api/base-branches": {
11990
+ GET: () => catching("GET /api/base-branches", () => apiListBaseBranches())
11991
+ },
11124
11992
  "/api/project": {
11125
11993
  GET: () => catching("GET /api/project", () => apiGetProject())
11126
11994
  },
@@ -11146,6 +12014,14 @@ Bun.serve({
11146
12014
  return catching(`POST /api/worktrees/${name}/open`, () => apiOpenWorktree(name));
11147
12015
  }
11148
12016
  },
12017
+ "/api/worktrees/:name/terminal-launch": {
12018
+ GET: (req) => {
12019
+ const name = decodeURIComponent(req.params.name);
12020
+ if (!isValidWorktreeName(name))
12021
+ return errorResponse("Invalid worktree name", 400);
12022
+ return catching(`GET /api/worktrees/${name}/terminal-launch`, () => apiGetNativeTerminalLaunch(name));
12023
+ }
12024
+ },
11149
12025
  "/api/worktrees/:name/close": {
11150
12026
  POST: (req) => {
11151
12027
  const name = decodeURIComponent(req.params.name);
@@ -11189,6 +12065,15 @@ Bun.serve({
11189
12065
  "/api/linear/issues": {
11190
12066
  GET: () => catching("GET /api/linear/issues", () => apiGetLinearIssues())
11191
12067
  },
12068
+ "/api/linear/auto-create": {
12069
+ PUT: (req) => catching("PUT /api/linear/auto-create", () => apiSetLinearAutoCreate(req))
12070
+ },
12071
+ "/api/github/auto-remove-on-merge": {
12072
+ PUT: (req) => catching("PUT /api/github/auto-remove-on-merge", () => apiSetAutoRemoveOnMerge(req))
12073
+ },
12074
+ "/api/pull-main": {
12075
+ POST: (req) => catching("POST /api/pull-main", () => apiPullMain(req))
12076
+ },
11192
12077
  "/api/ci-logs/:runId": {
11193
12078
  GET: (req) => catching(`GET /api/ci-logs/${req.params.runId}`, () => apiCiLogs(req.params.runId))
11194
12079
  },
@@ -11211,8 +12096,8 @@ Bun.serve({
11211
12096
  const url = new URL(req.url);
11212
12097
  const rawPath = url.pathname === "/" ? "index.html" : url.pathname;
11213
12098
  const filePath = join6(STATIC_DIR, rawPath);
11214
- const staticRoot = resolve6(STATIC_DIR);
11215
- if (!resolve6(filePath).startsWith(staticRoot + "/")) {
12099
+ const staticRoot = resolve7(STATIC_DIR);
12100
+ if (!resolve7(filePath).startsWith(staticRoot + "/")) {
11216
12101
  return new Response("Forbidden", { status: 403 });
11217
12102
  }
11218
12103
  const file = Bun.file(filePath);
@@ -11242,26 +12127,26 @@ Bun.serve({
11242
12127
  const { branch } = ws.data;
11243
12128
  switch (msg.type) {
11244
12129
  case "input": {
11245
- const worktreeId = getAttachedWorktreeId(ws);
11246
- if (!worktreeId)
12130
+ const attachId = getAttachedSessionId(ws);
12131
+ if (!attachId)
11247
12132
  return;
11248
- write(worktreeId, msg.data);
12133
+ write(attachId, msg.data);
11249
12134
  break;
11250
12135
  }
11251
12136
  case "sendKeys": {
11252
- const worktreeId = getAttachedWorktreeId(ws);
11253
- if (!worktreeId)
12137
+ const attachId = getAttachedSessionId(ws);
12138
+ if (!attachId)
11254
12139
  return;
11255
- await sendKeys(worktreeId, msg.hexBytes);
12140
+ await sendKeys(attachId, msg.hexBytes);
11256
12141
  break;
11257
12142
  }
11258
12143
  case "selectPane":
11259
12144
  {
11260
- const worktreeId = getAttachedWorktreeId(ws);
11261
- if (!worktreeId)
12145
+ const attachId = getAttachedSessionId(ws);
12146
+ if (!attachId)
11262
12147
  return;
11263
- log.debug(`[ws] selectPane pane=${msg.pane} branch=${branch} worktreeId=${worktreeId}`);
11264
- await selectPane(worktreeId, msg.pane);
12148
+ log.debug(`[ws] selectPane pane=${msg.pane} branch=${branch} attachId=${attachId}`);
12149
+ await selectPane(attachId, msg.pane);
11265
12150
  }
11266
12151
  break;
11267
12152
  case "resize":
@@ -11273,12 +12158,14 @@ Bun.serve({
11273
12158
  log.debug(`[ws] initialPane=${msg.initialPane} branch=${branch}`);
11274
12159
  }
11275
12160
  const terminalWorktree = await resolveTerminalWorktree(branch);
12161
+ const attachId = `${terminalWorktree.worktreeId}:${randomUUID3()}`;
11276
12162
  ws.data.worktreeId = terminalWorktree.worktreeId;
11277
- await attach(terminalWorktree.worktreeId, terminalWorktree.attachTarget, msg.cols, msg.rows, msg.initialPane);
12163
+ ws.data.attachId = attachId;
12164
+ await attach(attachId, terminalWorktree.attachTarget, msg.cols, msg.rows, msg.initialPane);
11278
12165
  const { onData, onExit } = makeCallbacks(ws);
11279
- setCallbacks(terminalWorktree.worktreeId, onData, onExit);
11280
- const scrollback = getScrollback(terminalWorktree.worktreeId);
11281
- log.debug(`[ws] attached branch=${branch} worktreeId=${terminalWorktree.worktreeId} scrollback=${scrollback.length} bytes`);
12166
+ setCallbacks(attachId, onData, onExit);
12167
+ const scrollback = getScrollback(attachId);
12168
+ log.debug(`[ws] attached branch=${branch} worktreeId=${terminalWorktree.worktreeId} attachId=${attachId} scrollback=${scrollback.length} bytes`);
11282
12169
  if (scrollback.length > 0) {
11283
12170
  sendWs(ws, { type: "scrollback", data: scrollback });
11284
12171
  }
@@ -11286,24 +12173,25 @@ Bun.serve({
11286
12173
  const errMsg = err instanceof Error ? err.message : String(err);
11287
12174
  ws.data.attached = false;
11288
12175
  ws.data.worktreeId = null;
12176
+ ws.data.attachId = null;
11289
12177
  log.error(`[ws] attach failed branch=${branch}: ${errMsg}`);
11290
12178
  sendWs(ws, { type: "error", message: errMsg });
11291
12179
  ws.close(1011, errMsg.slice(0, 123));
11292
12180
  }
11293
12181
  } else {
11294
- const worktreeId = getAttachedWorktreeId(ws);
11295
- if (!worktreeId)
12182
+ const attachId = getAttachedSessionId(ws);
12183
+ if (!attachId)
11296
12184
  return;
11297
- await resize(worktreeId, msg.cols, msg.rows);
12185
+ await resize(attachId, msg.cols, msg.rows);
11298
12186
  }
11299
12187
  break;
11300
12188
  }
11301
12189
  },
11302
12190
  async close(ws, code, reason) {
11303
- log.debug(`[ws] close branch=${ws.data.branch} code=${code} reason=${reason} attached=${ws.data.attached} worktreeId=${ws.data.worktreeId}`);
11304
- if (ws.data.worktreeId) {
11305
- clearCallbacks(ws.data.worktreeId);
11306
- await detach(ws.data.worktreeId);
12191
+ log.debug(`[ws] close branch=${ws.data.branch} code=${code} reason=${reason} attached=${ws.data.attached} worktreeId=${ws.data.worktreeId} attachId=${ws.data.attachId}`);
12192
+ if (ws.data.attachId) {
12193
+ clearCallbacks(ws.data.attachId);
12194
+ await detach(ws.data.attachId);
11307
12195
  }
11308
12196
  }
11309
12197
  }
@@ -11314,7 +12202,17 @@ if (tmuxCheck.exitCode !== 0) {
11314
12202
  log.info("Started tmux session");
11315
12203
  }
11316
12204
  cleanupStaleSessions();
11317
- startPrMonitor(getWorktreeGitDirs, config.integrations.github.linkedRepos, PROJECT_DIR, undefined, hasRecentDashboardActivity);
12205
+ startPrMonitor(getWorktreeGitDirs, config.integrations.github.linkedRepos, PROJECT_DIR, undefined, hasRecentDashboardActivity, async () => {
12206
+ if (autoRemoveOnMergeEnabled) {
12207
+ await runAutoRemove(autoRemoveDeps);
12208
+ }
12209
+ });
12210
+ if (linearAutoCreateEnabled) {
12211
+ startLinearAutoCreate();
12212
+ }
12213
+ if (config.workspace.autoPull.enabled) {
12214
+ startAutoPullMonitor({ git, projectRoot: PROJECT_DIR, mainBranch: config.workspace.mainBranch }, config.workspace.autoPull.intervalSeconds * 1000);
12215
+ }
11318
12216
  log.info(`Dev Dashboard API running at http://localhost:${PORT}`);
11319
12217
  var nets = networkInterfaces();
11320
12218
  for (const addrs of Object.values(nets)) {