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.
- package/README.md +15 -4
- package/backend/dist/server.js +1262 -364
- package/bin/webmux.js +576 -232
- package/frontend/dist/assets/{index-Cy8MTH3L.css → index-DGNuO17V.css} +1 -1
- package/frontend/dist/assets/index-DrHGpNfR.js +149 -0
- package/frontend/dist/icon.svg +1 -0
- package/frontend/dist/index.html +5 -7
- package/frontend/dist/manifest.webmanifest +16 -0
- package/package.json +1 -1
- package/frontend/dist/assets/index-CzRnnD-H.js +0 -148
package/backend/dist/server.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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(
|
|
7001
|
-
log.debug(`[term] attach(${
|
|
7002
|
-
if (sessions.has(
|
|
7003
|
-
await detach(
|
|
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(${
|
|
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
|
|
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(
|
|
7034
|
-
log.debug(`[term] attach(${
|
|
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(${
|
|
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(${
|
|
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(${
|
|
7072
|
-
if (sessions.get(
|
|
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(
|
|
7108
|
+
sessions.delete(attachId);
|
|
7075
7109
|
} else {
|
|
7076
|
-
log.debug(`[term] proc exited(${
|
|
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(
|
|
7082
|
-
const session = sessions.get(
|
|
7115
|
+
async function detach(attachId) {
|
|
7116
|
+
const session = sessions.get(attachId);
|
|
7083
7117
|
if (!session) {
|
|
7084
|
-
log.debug(`[term] detach(${
|
|
7118
|
+
log.debug(`[term] detach(${attachId}) no session found`);
|
|
7085
7119
|
return;
|
|
7086
7120
|
}
|
|
7087
|
-
log.debug(`[term] detach(${
|
|
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(
|
|
7124
|
+
sessions.delete(attachId);
|
|
7091
7125
|
killTmuxSession(session.groupedSessionName);
|
|
7092
7126
|
}
|
|
7093
|
-
function write(
|
|
7094
|
-
const session = sessions.get(
|
|
7127
|
+
function write(attachId, data) {
|
|
7128
|
+
const session = sessions.get(attachId);
|
|
7095
7129
|
if (!session) {
|
|
7096
|
-
log.warn(`[term] write(${
|
|
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(${
|
|
7137
|
+
log.error(`[term] write(${attachId}) stdin closed`, err);
|
|
7104
7138
|
}
|
|
7105
7139
|
}
|
|
7106
|
-
async function sendKeys(
|
|
7107
|
-
const session = sessions.get(
|
|
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(
|
|
7114
|
-
const session = sessions.get(
|
|
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(
|
|
7123
|
-
return sessions.get(
|
|
7156
|
+
function getScrollback(attachId) {
|
|
7157
|
+
return sessions.get(attachId)?.scrollback.join("") ?? "";
|
|
7124
7158
|
}
|
|
7125
|
-
function setCallbacks(
|
|
7126
|
-
const session = sessions.get(
|
|
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(
|
|
7133
|
-
const session = sessions.get(
|
|
7166
|
+
async function selectPane(attachId, paneIndex) {
|
|
7167
|
+
const session = sessions.get(attachId);
|
|
7134
7168
|
if (!session) {
|
|
7135
|
-
log.debug(`[term] selectPane(${
|
|
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(${
|
|
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(${
|
|
7178
|
+
log.debug(`[term] selectPane(${attachId}) select=${r1.exitCode} zoom=${r2.exitCode}`);
|
|
7145
7179
|
}
|
|
7146
|
-
function clearCallbacks(
|
|
7147
|
-
const session = sessions.get(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
7606
|
-
|
|
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
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
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
|
-
|
|
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:
|
|
7942
|
+
body: JSON.stringify(variables ? { query, variables } : { query })
|
|
7670
7943
|
});
|
|
7671
7944
|
if (!res.ok) {
|
|
7672
7945
|
const text = await res.text();
|
|
7673
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8689
|
+
return ctx.paneCommands.agent;
|
|
8325
8690
|
case "shell":
|
|
8326
8691
|
return;
|
|
8327
8692
|
case "command":
|
|
8328
|
-
|
|
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
|
|
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
|
|
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 =
|
|
8436
|
-
return entries.some((entry) =>
|
|
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
|
|
8821
|
+
return resolve4(cwd, output);
|
|
8460
8822
|
}
|
|
8461
8823
|
function resolveWorktreeGitDir(cwd) {
|
|
8462
8824
|
const output = runGit(["rev-parse", "--git-dir"], cwd);
|
|
8463
|
-
return
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
9456
|
+
return resolve5(this.deps.projectRoot, this.deps.config.workspace.worktreeRoot, branch);
|
|
9065
9457
|
}
|
|
9066
9458
|
listLocalBranches() {
|
|
9067
|
-
return this.deps.git.listLocalBranches(
|
|
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(
|
|
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 =
|
|
9074
|
-
return this.deps.git.listWorktrees(projectRoot2).filter((entry) => !entry.bare &&
|
|
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
|
-
|
|
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
|
-
|
|
9674
|
+
log.debug(`[lifecycle-hook] SKIPPING ${input.name}: command=${!!input.command} meta=${!!input.meta}`);
|
|
9280
9675
|
return;
|
|
9281
9676
|
}
|
|
9282
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
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 =
|
|
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
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
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
|
-
|
|
10062
|
-
|
|
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
|
-
|
|
10744
|
+
log.debug(`[hook-runner] ${input.name} exitCode=${exitCode}`);
|
|
10078
10745
|
if (stdout.trim())
|
|
10079
|
-
|
|
10746
|
+
log.debug(`[hook-runner] stdout: ${stdout.trim()}`);
|
|
10080
10747
|
if (stderr.trim())
|
|
10081
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
10780
|
+
resolve6(false);
|
|
10114
10781
|
}
|
|
10115
10782
|
};
|
|
10116
10783
|
const timer = setTimeout(() => {
|
|
10117
10784
|
if (!settled) {
|
|
10118
10785
|
settled = true;
|
|
10119
|
-
|
|
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
|
|
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
|
-
|
|
10308
|
-
const notification =
|
|
10309
|
-
|
|
10310
|
-
|
|
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
|
|
11186
|
+
import { basename as basename2, resolve as resolve6 } from "path";
|
|
10525
11187
|
function makeUnmanagedWorktreeId(path) {
|
|
10526
|
-
return `unmanaged:${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10580
|
-
|
|
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
|
-
|
|
10589
|
-
|
|
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
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
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
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
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:
|
|
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
|
-
|
|
10833
|
-
|
|
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
|
|
10849
|
-
|
|
10850
|
-
|
|
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 =
|
|
11616
|
+
const projectRoot2 = resolve7(PROJECT_DIR);
|
|
10863
11617
|
for (const entry of git.listWorktrees(projectRoot2)) {
|
|
10864
|
-
if (entry.bare ||
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
10962
|
-
|
|
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 (
|
|
10966
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
11215
|
-
if (!
|
|
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
|
|
11246
|
-
if (!
|
|
12130
|
+
const attachId = getAttachedSessionId(ws);
|
|
12131
|
+
if (!attachId)
|
|
11247
12132
|
return;
|
|
11248
|
-
write(
|
|
12133
|
+
write(attachId, msg.data);
|
|
11249
12134
|
break;
|
|
11250
12135
|
}
|
|
11251
12136
|
case "sendKeys": {
|
|
11252
|
-
const
|
|
11253
|
-
if (!
|
|
12137
|
+
const attachId = getAttachedSessionId(ws);
|
|
12138
|
+
if (!attachId)
|
|
11254
12139
|
return;
|
|
11255
|
-
await sendKeys(
|
|
12140
|
+
await sendKeys(attachId, msg.hexBytes);
|
|
11256
12141
|
break;
|
|
11257
12142
|
}
|
|
11258
12143
|
case "selectPane":
|
|
11259
12144
|
{
|
|
11260
|
-
const
|
|
11261
|
-
if (!
|
|
12145
|
+
const attachId = getAttachedSessionId(ws);
|
|
12146
|
+
if (!attachId)
|
|
11262
12147
|
return;
|
|
11263
|
-
log.debug(`[ws] selectPane pane=${msg.pane} branch=${branch}
|
|
11264
|
-
await selectPane(
|
|
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
|
-
|
|
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(
|
|
11280
|
-
const scrollback = getScrollback(
|
|
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
|
|
11295
|
-
if (!
|
|
12182
|
+
const attachId = getAttachedSessionId(ws);
|
|
12183
|
+
if (!attachId)
|
|
11296
12184
|
return;
|
|
11297
|
-
await resize(
|
|
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.
|
|
11305
|
-
clearCallbacks(ws.data.
|
|
11306
|
-
await detach(ws.data.
|
|
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)) {
|