@sma1lboy/kobe 0.7.23 → 0.7.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +864 -189
- package/dist/web-ui/assets/AppShell-BUxSw0xG.js +7 -0
- package/dist/web-ui/assets/{ChatTerminal-j3zmSA6m.js → ChatTerminal-DL9kz6iJ.js} +2 -2
- package/dist/web-ui/assets/ChatTranscript-DL4KGcCP.js +11 -0
- package/dist/web-ui/assets/arrow-left-CPDGLwQs.js +1 -0
- package/dist/web-ui/assets/board-CbwyUj3Z.js +7 -0
- package/dist/web-ui/assets/chips-BYg-ofbe.js +2 -0
- package/dist/web-ui/assets/index-CtJIPdVH.css +2 -0
- package/dist/web-ui/assets/index-DdSnT7Rv.js +10 -0
- package/dist/web-ui/assets/overview-BFm-fRcJ.js +1 -0
- package/dist/web-ui/assets/react-B8IZ02wI.js +1 -0
- package/dist/web-ui/assets/routes-B6JWLjSF.js +1 -0
- package/dist/web-ui/assets/task._taskId-CnOx0dEd.js +1 -0
- package/dist/web-ui/assets/terminal-D7PGfbSa.js +1 -0
- package/dist/web-ui/assets/triage-89UXpkZl.js +1 -0
- package/dist/web-ui/index.html +3 -3
- package/dist/web-ui/pty-scrollback.mjs +51 -0
- package/dist/web-ui/pty-server.mjs +85 -0
- package/package.json +1 -1
- package/dist/web-ui/assets/AppShell-C0hhV6qB.js +0 -16
- package/dist/web-ui/assets/index-BPEy1cTR.css +0 -2
- package/dist/web-ui/assets/index-CMnpXQhx.js +0 -10
- package/dist/web-ui/assets/jsx-runtime-bzQ4Vb5N.js +0 -1
- package/dist/web-ui/assets/overview-Gj7Z3GGU.js +0 -1
- package/dist/web-ui/assets/routes-BQZWyGJi.js +0 -1
- package/dist/web-ui/assets/task._taskId-CF3ph7W5.js +0 -1
- package/dist/web-ui/assets/triage-DT7sWoTl.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
|
|
|
90
90
|
package_default = {
|
|
91
91
|
$schema: "https://json.schemastore.org/package.json",
|
|
92
92
|
name: "@sma1lboy/kobe",
|
|
93
|
-
version: "0.7.
|
|
93
|
+
version: "0.7.25",
|
|
94
94
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
95
95
|
type: "module",
|
|
96
96
|
packageManager: "bun@1.3.13",
|
|
@@ -1192,6 +1192,37 @@ class TaskIndexStore {
|
|
|
1192
1192
|
this.notifyListeners();
|
|
1193
1193
|
return next;
|
|
1194
1194
|
}
|
|
1195
|
+
async reorder(moves) {
|
|
1196
|
+
this.assertLoaded();
|
|
1197
|
+
const resolved = moves.map((move) => {
|
|
1198
|
+
const idx = this.cache.tasks.findIndex((t) => t.id === move.id);
|
|
1199
|
+
const existing = idx >= 0 ? this.cache.tasks[idx] : undefined;
|
|
1200
|
+
if (!existing)
|
|
1201
|
+
throw new Error(`task not found: ${move.id}`);
|
|
1202
|
+
return { idx, position: move.position };
|
|
1203
|
+
});
|
|
1204
|
+
let dirty = false;
|
|
1205
|
+
const before = new Map;
|
|
1206
|
+
for (const { idx, position } of resolved) {
|
|
1207
|
+
const existing = this.cache.tasks[idx];
|
|
1208
|
+
if (!existing || existing.position === position)
|
|
1209
|
+
continue;
|
|
1210
|
+
if (!before.has(idx))
|
|
1211
|
+
before.set(idx, existing);
|
|
1212
|
+
this.cache.tasks[idx] = { ...existing, position };
|
|
1213
|
+
dirty = true;
|
|
1214
|
+
}
|
|
1215
|
+
if (!dirty)
|
|
1216
|
+
return;
|
|
1217
|
+
try {
|
|
1218
|
+
await this.save();
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
for (const [idx, task] of before)
|
|
1221
|
+
this.cache.tasks[idx] = task;
|
|
1222
|
+
throw err;
|
|
1223
|
+
}
|
|
1224
|
+
this.notifyListeners();
|
|
1225
|
+
}
|
|
1195
1226
|
async archive(id, status = "done") {
|
|
1196
1227
|
return this.update(id, { status });
|
|
1197
1228
|
}
|
|
@@ -1291,6 +1322,7 @@ function coerceTask(value) {
|
|
|
1291
1322
|
kind,
|
|
1292
1323
|
vendor: isVendorId(v.vendor) ? v.vendor : DEFAULT_TASK_VENDOR,
|
|
1293
1324
|
prStatus: coercePRStatus(v.prStatus),
|
|
1325
|
+
...typeof v.position === "number" && Number.isFinite(v.position) ? { position: v.position } : {},
|
|
1294
1326
|
createdAt: v.createdAt,
|
|
1295
1327
|
updatedAt: v.updatedAt
|
|
1296
1328
|
};
|
|
@@ -3770,6 +3802,18 @@ class Orchestrator {
|
|
|
3770
3802
|
return;
|
|
3771
3803
|
await this.store.update(task.id, { archived: next });
|
|
3772
3804
|
}
|
|
3805
|
+
async reorderTasks(moves) {
|
|
3806
|
+
if (moves.length === 0)
|
|
3807
|
+
return;
|
|
3808
|
+
for (const move of moves) {
|
|
3809
|
+
const task = this.requireTask(move.taskId);
|
|
3810
|
+
if (task.kind === "main")
|
|
3811
|
+
throw new Error(`cannot reorder a main task: ${move.taskId}`);
|
|
3812
|
+
if (!Number.isFinite(move.position))
|
|
3813
|
+
throw new Error(`position must be a finite number: ${move.taskId}`);
|
|
3814
|
+
}
|
|
3815
|
+
await this.store.reorder(moves.map((move) => ({ id: move.taskId, position: move.position })));
|
|
3816
|
+
}
|
|
3773
3817
|
async setStatus(id, status) {
|
|
3774
3818
|
const task = this.requireTask(id);
|
|
3775
3819
|
if (task.status === status)
|
|
@@ -3916,6 +3960,7 @@ function serializeTask(task) {
|
|
|
3916
3960
|
pinned: task.pinned ?? false,
|
|
3917
3961
|
vendor: task.vendor,
|
|
3918
3962
|
prStatus: task.prStatus,
|
|
3963
|
+
position: task.position,
|
|
3919
3964
|
createdAt: task.createdAt,
|
|
3920
3965
|
updatedAt: task.updatedAt
|
|
3921
3966
|
};
|
|
@@ -3934,7 +3979,9 @@ var init_protocol = __esm(() => {
|
|
|
3934
3979
|
"ui-prefs",
|
|
3935
3980
|
"keybindings",
|
|
3936
3981
|
"task.jobs",
|
|
3937
|
-
"worktree.changes"
|
|
3982
|
+
"worktree.changes",
|
|
3983
|
+
"task.conflicts",
|
|
3984
|
+
"session.deliver"
|
|
3938
3985
|
];
|
|
3939
3986
|
CHANNEL_NAME_SET = new Set(CHANNEL_NAMES);
|
|
3940
3987
|
});
|
|
@@ -4217,9 +4264,12 @@ class KobeDaemonClient {
|
|
|
4217
4264
|
if (!pending)
|
|
4218
4265
|
return;
|
|
4219
4266
|
this.pending.delete(frame.id);
|
|
4220
|
-
if (frame.error)
|
|
4221
|
-
|
|
4222
|
-
|
|
4267
|
+
if (frame.error) {
|
|
4268
|
+
const err = new Error(frame.error.message);
|
|
4269
|
+
if (frame.error.name)
|
|
4270
|
+
err.name = frame.error.name;
|
|
4271
|
+
pending.reject(err);
|
|
4272
|
+
} else
|
|
4223
4273
|
pending.resolve(frame.payload);
|
|
4224
4274
|
}
|
|
4225
4275
|
emit(frame) {
|
|
@@ -6843,6 +6893,347 @@ var init_auto_title_poller = __esm(() => {
|
|
|
6843
6893
|
init_chat_tab_naming();
|
|
6844
6894
|
});
|
|
6845
6895
|
|
|
6896
|
+
// src/lib/poll-scheduling.ts
|
|
6897
|
+
import { spawn as spawn2 } from "child_process";
|
|
6898
|
+
function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
|
|
6899
|
+
if (timedOut)
|
|
6900
|
+
return startedAt + cfg.slowRetryMs;
|
|
6901
|
+
return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
|
|
6902
|
+
}
|
|
6903
|
+
function shouldPoll(state, now) {
|
|
6904
|
+
return !state.inFlight && now >= state.nextAllowedAt;
|
|
6905
|
+
}
|
|
6906
|
+
function maybeStartScheduledRun(state, cfg, run, onValue) {
|
|
6907
|
+
const startedAt = Date.now();
|
|
6908
|
+
if (!shouldPoll(state, startedAt))
|
|
6909
|
+
return false;
|
|
6910
|
+
state.inFlight = true;
|
|
6911
|
+
const controller = new AbortController;
|
|
6912
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
6913
|
+
(async () => {
|
|
6914
|
+
let value;
|
|
6915
|
+
let ok = false;
|
|
6916
|
+
try {
|
|
6917
|
+
value = await run(controller.signal);
|
|
6918
|
+
ok = true;
|
|
6919
|
+
} catch {}
|
|
6920
|
+
clearTimeout(timer);
|
|
6921
|
+
const timedOut = controller.signal.aborted;
|
|
6922
|
+
state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
|
|
6923
|
+
state.inFlight = false;
|
|
6924
|
+
if (ok && !timedOut)
|
|
6925
|
+
onValue(value);
|
|
6926
|
+
})();
|
|
6927
|
+
return true;
|
|
6928
|
+
}
|
|
6929
|
+
function spawnCapture(cmd, args, opts) {
|
|
6930
|
+
return new Promise((resolve2) => {
|
|
6931
|
+
let out = "";
|
|
6932
|
+
let settled = false;
|
|
6933
|
+
const finish = (status) => {
|
|
6934
|
+
if (settled)
|
|
6935
|
+
return;
|
|
6936
|
+
settled = true;
|
|
6937
|
+
resolve2({ status, stdout: out });
|
|
6938
|
+
};
|
|
6939
|
+
const child = spawn2(cmd, args.slice(), {
|
|
6940
|
+
cwd: opts.cwd,
|
|
6941
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
6942
|
+
env: opts.env,
|
|
6943
|
+
signal: opts.signal,
|
|
6944
|
+
killSignal: "SIGKILL"
|
|
6945
|
+
});
|
|
6946
|
+
child.stdout?.on("data", (chunk) => {
|
|
6947
|
+
out += String(chunk);
|
|
6948
|
+
});
|
|
6949
|
+
child.on("error", () => finish(null));
|
|
6950
|
+
child.on("close", (code) => finish(code));
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
var init_poll_scheduling = () => {};
|
|
6954
|
+
|
|
6955
|
+
// ../kobe-daemon/src/daemon/conflict-collector.ts
|
|
6956
|
+
class GitGate {
|
|
6957
|
+
limit;
|
|
6958
|
+
active = 0;
|
|
6959
|
+
waiters = [];
|
|
6960
|
+
constructor(limit = MAX_CONCURRENT_GIT) {
|
|
6961
|
+
this.limit = limit;
|
|
6962
|
+
}
|
|
6963
|
+
async run(fn) {
|
|
6964
|
+
if (this.active >= this.limit) {
|
|
6965
|
+
await new Promise((resolve2) => this.waiters.push(resolve2));
|
|
6966
|
+
}
|
|
6967
|
+
this.active += 1;
|
|
6968
|
+
try {
|
|
6969
|
+
return await fn();
|
|
6970
|
+
} finally {
|
|
6971
|
+
this.active -= 1;
|
|
6972
|
+
this.waiters.shift()?.();
|
|
6973
|
+
}
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
async function git(cwd, args, signal, gate) {
|
|
6977
|
+
return gate.run(() => spawnCapture("git", args, { cwd, env: { ...process.env, ...LOCK_FREE_ENV }, signal }));
|
|
6978
|
+
}
|
|
6979
|
+
function parsePorcelainPaths(stdout) {
|
|
6980
|
+
const paths = [];
|
|
6981
|
+
for (const line of stdout.split(`
|
|
6982
|
+
`)) {
|
|
6983
|
+
if (line.length < 4)
|
|
6984
|
+
continue;
|
|
6985
|
+
const rest = line.slice(3);
|
|
6986
|
+
const arrow = rest.indexOf(" -> ");
|
|
6987
|
+
if (arrow >= 0) {
|
|
6988
|
+
paths.push(rest.slice(0, arrow), rest.slice(arrow + 4));
|
|
6989
|
+
} else {
|
|
6990
|
+
paths.push(rest);
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
return paths;
|
|
6994
|
+
}
|
|
6995
|
+
async function resolveBaseRef(worktree, signal, gate) {
|
|
6996
|
+
for (const ref of BASE_REF_CANDIDATES) {
|
|
6997
|
+
const res = await git(worktree, ["rev-parse", "--verify", "--quiet", ref], signal, gate);
|
|
6998
|
+
if (res.status === 0)
|
|
6999
|
+
return ref;
|
|
7000
|
+
}
|
|
7001
|
+
return null;
|
|
7002
|
+
}
|
|
7003
|
+
async function collectFootprint(worktree, repo, baseRef, signal, gate) {
|
|
7004
|
+
const head = await git(worktree, ["rev-parse", "HEAD"], signal, gate);
|
|
7005
|
+
if (head.status !== 0)
|
|
7006
|
+
throw new Error("rev-parse HEAD failed");
|
|
7007
|
+
const files = new Set;
|
|
7008
|
+
const status = await git(worktree, ["status", "--porcelain=v1"], signal, gate);
|
|
7009
|
+
if (status.status !== 0)
|
|
7010
|
+
throw new Error("git status failed");
|
|
7011
|
+
for (const p of parsePorcelainPaths(status.stdout))
|
|
7012
|
+
files.add(p);
|
|
7013
|
+
if (baseRef) {
|
|
7014
|
+
const diff = await git(worktree, ["diff", "--name-only", `${baseRef}...HEAD`], signal, gate);
|
|
7015
|
+
if (diff.status === 0) {
|
|
7016
|
+
for (const p of diff.stdout.split(`
|
|
7017
|
+
`))
|
|
7018
|
+
if (p)
|
|
7019
|
+
files.add(p);
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
return { repo, head: head.stdout.trim(), files };
|
|
7023
|
+
}
|
|
7024
|
+
function sameFootprint(a, b) {
|
|
7025
|
+
if (a.head !== b.head || a.files.size !== b.files.size)
|
|
7026
|
+
return false;
|
|
7027
|
+
for (const f of a.files)
|
|
7028
|
+
if (!b.files.has(f))
|
|
7029
|
+
return false;
|
|
7030
|
+
return true;
|
|
7031
|
+
}
|
|
7032
|
+
function trackedConflictTasks(tasks) {
|
|
7033
|
+
return tasks.filter((t) => !t.archived && (t.kind ?? "task") !== "main" && !!t.worktreePath && !isRemoteRepoKey(t.repo) && !isRemoteRepoKey(t.worktreePath));
|
|
7034
|
+
}
|
|
7035
|
+
function overlapPairs(cards) {
|
|
7036
|
+
const ids = [...cards.keys()].sort();
|
|
7037
|
+
const pairs = [];
|
|
7038
|
+
for (let i = 0;i < ids.length; i++) {
|
|
7039
|
+
for (let j = i + 1;j < ids.length; j++) {
|
|
7040
|
+
const a = cards.get(ids[i]);
|
|
7041
|
+
const b = cards.get(ids[j]);
|
|
7042
|
+
if (a.repo !== b.repo)
|
|
7043
|
+
continue;
|
|
7044
|
+
const files = [...a.files].filter((f) => b.files.has(f)).sort();
|
|
7045
|
+
if (files.length === 0)
|
|
7046
|
+
continue;
|
|
7047
|
+
pairs.push({ a: ids[i], b: ids[j], files, level: "overlap" });
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
7050
|
+
return pairs;
|
|
7051
|
+
}
|
|
7052
|
+
function parseMergeTreeNames(stdout) {
|
|
7053
|
+
const lines = stdout.split(`
|
|
7054
|
+
`);
|
|
7055
|
+
const names = [];
|
|
7056
|
+
for (const line of lines.slice(1)) {
|
|
7057
|
+
if (!line)
|
|
7058
|
+
break;
|
|
7059
|
+
names.push(line);
|
|
7060
|
+
}
|
|
7061
|
+
return names;
|
|
7062
|
+
}
|
|
7063
|
+
function samePairs(a, b) {
|
|
7064
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
7065
|
+
}
|
|
7066
|
+
|
|
7067
|
+
class ConflictCollector {
|
|
7068
|
+
orch;
|
|
7069
|
+
bus;
|
|
7070
|
+
options;
|
|
7071
|
+
entries = new Map;
|
|
7072
|
+
baseRefs = new Map;
|
|
7073
|
+
mergeProbes = new Map;
|
|
7074
|
+
gate = new GitGate;
|
|
7075
|
+
mergeTreeUnsupported = false;
|
|
7076
|
+
lastPublished = [];
|
|
7077
|
+
stopped = false;
|
|
7078
|
+
constructor(orch, bus, options = {}) {
|
|
7079
|
+
this.orch = orch;
|
|
7080
|
+
this.bus = bus;
|
|
7081
|
+
this.options = options;
|
|
7082
|
+
}
|
|
7083
|
+
tick() {
|
|
7084
|
+
if (this.stopped)
|
|
7085
|
+
return;
|
|
7086
|
+
if (this.options.hasSubscribers && !this.options.hasSubscribers())
|
|
7087
|
+
return;
|
|
7088
|
+
try {
|
|
7089
|
+
const tracked = trackedConflictTasks(this.orch.listTasks());
|
|
7090
|
+
const trackedIds = new Set(tracked.map((t) => t.id));
|
|
7091
|
+
let pruned = false;
|
|
7092
|
+
for (const id of this.entries.keys()) {
|
|
7093
|
+
if (trackedIds.has(id))
|
|
7094
|
+
continue;
|
|
7095
|
+
if (this.entries.get(id)?.value)
|
|
7096
|
+
pruned = true;
|
|
7097
|
+
this.entries.delete(id);
|
|
7098
|
+
}
|
|
7099
|
+
if (pruned)
|
|
7100
|
+
this.recompute();
|
|
7101
|
+
for (const task of tracked)
|
|
7102
|
+
this.maybeCollect(task);
|
|
7103
|
+
} catch (err) {
|
|
7104
|
+
logDaemonError("conflict-radar", err);
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
stop() {
|
|
7108
|
+
this.stopped = true;
|
|
7109
|
+
}
|
|
7110
|
+
maybeCollect(task) {
|
|
7111
|
+
const id = task.id;
|
|
7112
|
+
let entry = this.entries.get(id);
|
|
7113
|
+
if (!entry) {
|
|
7114
|
+
entry = { inFlight: false, nextAllowedAt: 0 };
|
|
7115
|
+
this.entries.set(id, entry);
|
|
7116
|
+
}
|
|
7117
|
+
const cadence = this.options.cadence ?? {
|
|
7118
|
+
timeoutMs: CONFLICTS_TIMEOUT_MS,
|
|
7119
|
+
slowRetryMs: CONFLICTS_SLOW_RETRY_MS,
|
|
7120
|
+
minIntervalMs: CONFLICTS_MIN_INTERVAL_MS
|
|
7121
|
+
};
|
|
7122
|
+
const run = this.options.footprint ?? (async (t, signal) => {
|
|
7123
|
+
const baseRef = await this.baseRefFor(t.worktreePath, signal);
|
|
7124
|
+
return collectFootprint(t.worktreePath, t.repo, baseRef, signal, this.gate);
|
|
7125
|
+
});
|
|
7126
|
+
maybeStartScheduledRun(entry, cadence, (signal) => run(task, signal), (value) => {
|
|
7127
|
+
if (this.stopped)
|
|
7128
|
+
return;
|
|
7129
|
+
if (this.entries.get(id) !== entry)
|
|
7130
|
+
return;
|
|
7131
|
+
if (entry.value && sameFootprint(entry.value, value))
|
|
7132
|
+
return;
|
|
7133
|
+
entry.value = value;
|
|
7134
|
+
this.recompute();
|
|
7135
|
+
});
|
|
7136
|
+
}
|
|
7137
|
+
baseRefFor(worktree, signal) {
|
|
7138
|
+
const cached = this.baseRefs.get(worktree);
|
|
7139
|
+
if (cached)
|
|
7140
|
+
return cached;
|
|
7141
|
+
const promise = resolveBaseRef(worktree, signal, this.gate).catch(() => null);
|
|
7142
|
+
this.baseRefs.set(worktree, promise);
|
|
7143
|
+
return promise;
|
|
7144
|
+
}
|
|
7145
|
+
recompute() {
|
|
7146
|
+
const cards = new Map;
|
|
7147
|
+
for (const [id, entry] of this.entries) {
|
|
7148
|
+
if (entry.value)
|
|
7149
|
+
cards.set(id, entry.value);
|
|
7150
|
+
}
|
|
7151
|
+
const pairs = overlapPairs(cards);
|
|
7152
|
+
const resolved = [];
|
|
7153
|
+
for (const pair of pairs) {
|
|
7154
|
+
const a = cards.get(pair.a);
|
|
7155
|
+
const b = cards.get(pair.b);
|
|
7156
|
+
const key = [a.repo, ...[a.head, b.head].sort()].join("\x00");
|
|
7157
|
+
const probe2 = this.mergeProbes.get(key);
|
|
7158
|
+
if (probe2?.state === "conflict") {
|
|
7159
|
+
resolved.push({
|
|
7160
|
+
...pair,
|
|
7161
|
+
level: "conflict",
|
|
7162
|
+
files: probe2.files.length > 0 ? probe2.files : pair.files
|
|
7163
|
+
});
|
|
7164
|
+
continue;
|
|
7165
|
+
}
|
|
7166
|
+
resolved.push(pair);
|
|
7167
|
+
if (!probe2 && !this.mergeTreeUnsupported && a.head !== b.head) {
|
|
7168
|
+
this.scheduleMergeProbe(key, cards, pair);
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
if (samePairs(this.lastPublished, resolved))
|
|
7172
|
+
return;
|
|
7173
|
+
this.lastPublished = resolved;
|
|
7174
|
+
const payload = { pairs: resolved };
|
|
7175
|
+
this.bus.publish("task.conflicts", payload);
|
|
7176
|
+
}
|
|
7177
|
+
scheduleMergeProbe(key, cards, pair) {
|
|
7178
|
+
const a = cards.get(pair.a);
|
|
7179
|
+
const b = cards.get(pair.b);
|
|
7180
|
+
this.mergeProbes.set(key, { state: "pending" });
|
|
7181
|
+
const worktree = this.worktreeOf(pair.a);
|
|
7182
|
+
const probe2 = this.options.probeMerge ?? (async (wt, headA, headB) => {
|
|
7183
|
+
const res = await git(wt, ["merge-tree", "--write-tree", "--name-only", headA, headB], AbortSignal.timeout(MERGE_TREE_TIMEOUT_MS), this.gate);
|
|
7184
|
+
if (res.status === 0)
|
|
7185
|
+
return { conflict: false, files: [] };
|
|
7186
|
+
if (res.status === 1)
|
|
7187
|
+
return { conflict: true, files: parseMergeTreeNames(res.stdout) };
|
|
7188
|
+
return null;
|
|
7189
|
+
});
|
|
7190
|
+
if (!worktree) {
|
|
7191
|
+
this.mergeProbes.delete(key);
|
|
7192
|
+
return;
|
|
7193
|
+
}
|
|
7194
|
+
probe2(worktree, a.head, b.head).then((result) => {
|
|
7195
|
+
if (this.stopped)
|
|
7196
|
+
return;
|
|
7197
|
+
if (result === null) {
|
|
7198
|
+
if (!this.mergeTreeUnsupported) {
|
|
7199
|
+
this.mergeTreeUnsupported = true;
|
|
7200
|
+
console.log("[conflict-radar] merge-tree dry-run unavailable (git < 2.38 or no merge base) \u2014 radar degrades to file-overlap only");
|
|
7201
|
+
}
|
|
7202
|
+
this.mergeProbes.delete(key);
|
|
7203
|
+
return;
|
|
7204
|
+
}
|
|
7205
|
+
this.mergeProbes.set(key, result.conflict ? { state: "conflict", files: result.files } : { state: "clean" });
|
|
7206
|
+
this.recompute();
|
|
7207
|
+
}).catch((err) => {
|
|
7208
|
+
this.mergeProbes.delete(key);
|
|
7209
|
+
logDaemonError("conflict-radar", err);
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
worktreeOf(taskId) {
|
|
7213
|
+
const task = this.orch.listTasks().find((t) => t.id === taskId);
|
|
7214
|
+
return task?.worktreePath || undefined;
|
|
7215
|
+
}
|
|
7216
|
+
}
|
|
7217
|
+
function startConflictCollector(orch, bus, tickMs = DEFAULT_CONFLICTS_TICK_MS, hasSubscribers) {
|
|
7218
|
+
if (tickMs <= 0)
|
|
7219
|
+
return () => {};
|
|
7220
|
+
const collector = new ConflictCollector(orch, bus, { hasSubscribers });
|
|
7221
|
+
collector.tick();
|
|
7222
|
+
const timer = setInterval(() => collector.tick(), tickMs);
|
|
7223
|
+
timer.unref?.();
|
|
7224
|
+
return () => {
|
|
7225
|
+
clearInterval(timer);
|
|
7226
|
+
collector.stop();
|
|
7227
|
+
};
|
|
7228
|
+
}
|
|
7229
|
+
var DEFAULT_CONFLICTS_TICK_MS = 5000, CONFLICTS_TIMEOUT_MS = 5000, CONFLICTS_SLOW_RETRY_MS = 60000, CONFLICTS_MIN_INTERVAL_MS = 1e4, MAX_CONCURRENT_GIT = 3, MERGE_TREE_TIMEOUT_MS = 8000, LOCK_FREE_ENV, BASE_REF_CANDIDATES;
|
|
7230
|
+
var init_conflict_collector = __esm(() => {
|
|
7231
|
+
init_poll_scheduling();
|
|
7232
|
+
init_repos();
|
|
7233
|
+
LOCK_FREE_ENV = { GIT_OPTIONAL_LOCKS: "0" };
|
|
7234
|
+
BASE_REF_CANDIDATES = ["origin/HEAD", "origin/main", "origin/master", "main", "master"];
|
|
7235
|
+
});
|
|
7236
|
+
|
|
6846
7237
|
// ../kobe-daemon/src/daemon/event-bus.ts
|
|
6847
7238
|
class DaemonEventBus {
|
|
6848
7239
|
last = new Map;
|
|
@@ -6864,6 +7255,33 @@ class DaemonEventBus {
|
|
|
6864
7255
|
}
|
|
6865
7256
|
}
|
|
6866
7257
|
|
|
7258
|
+
// src/state/auto-status.ts
|
|
7259
|
+
function autoStatusEnabled() {
|
|
7260
|
+
return loadStateFile()[AUTO_STATUS_KEY] === true;
|
|
7261
|
+
}
|
|
7262
|
+
var AUTO_STATUS_KEY = "experimental.autoStatus";
|
|
7263
|
+
var init_auto_status = __esm(() => {
|
|
7264
|
+
init_store();
|
|
7265
|
+
});
|
|
7266
|
+
|
|
7267
|
+
// src/monitor/status-rules.ts
|
|
7268
|
+
async function maybeAutoStart(orch, taskId, enabled = autoStatusEnabled) {
|
|
7269
|
+
if (!enabled())
|
|
7270
|
+
return "skipped";
|
|
7271
|
+
const task = orch.getTask(taskId);
|
|
7272
|
+
if (!task)
|
|
7273
|
+
return "skipped";
|
|
7274
|
+
if ((task.kind ?? "task") === "main" || task.archived)
|
|
7275
|
+
return "skipped";
|
|
7276
|
+
if (task.status !== "backlog")
|
|
7277
|
+
return "skipped";
|
|
7278
|
+
await orch.setStatus(taskId, "in_progress");
|
|
7279
|
+
return "moved";
|
|
7280
|
+
}
|
|
7281
|
+
var init_status_rules = __esm(() => {
|
|
7282
|
+
init_auto_status();
|
|
7283
|
+
});
|
|
7284
|
+
|
|
6867
7285
|
// ../kobe-daemon/src/daemon/cwd-task.ts
|
|
6868
7286
|
import { createHash as createHash4 } from "crypto";
|
|
6869
7287
|
import { homedir as homedir12 } from "os";
|
|
@@ -7128,6 +7546,29 @@ function createDaemonHandlerRegistry() {
|
|
|
7128
7546
|
return {};
|
|
7129
7547
|
}
|
|
7130
7548
|
},
|
|
7549
|
+
{
|
|
7550
|
+
name: "task.reorder",
|
|
7551
|
+
async handle(payload, ctx) {
|
|
7552
|
+
const moves = payload.moves;
|
|
7553
|
+
if (!Array.isArray(moves) || moves.length === 0)
|
|
7554
|
+
throw new Error("moves must be a non-empty array");
|
|
7555
|
+
if (moves.length > 500)
|
|
7556
|
+
throw new Error("too many moves in one task.reorder batch (max 500)");
|
|
7557
|
+
const parsed = moves.map((move) => {
|
|
7558
|
+
if (typeof move !== "object" || move === null)
|
|
7559
|
+
throw new Error("each move needs taskId and position");
|
|
7560
|
+
const entry = move;
|
|
7561
|
+
const taskId = requireString(entry, "taskId");
|
|
7562
|
+
const position = entry.position;
|
|
7563
|
+
if (typeof position !== "number" || !Number.isFinite(position)) {
|
|
7564
|
+
throw new Error("position must be a finite number");
|
|
7565
|
+
}
|
|
7566
|
+
return { taskId, position };
|
|
7567
|
+
});
|
|
7568
|
+
await ctx.orch.reorderTasks(parsed);
|
|
7569
|
+
return {};
|
|
7570
|
+
}
|
|
7571
|
+
},
|
|
7131
7572
|
{
|
|
7132
7573
|
name: "task.ensureMain",
|
|
7133
7574
|
async handle(payload, ctx) {
|
|
@@ -7204,6 +7645,47 @@ function createDaemonHandlerRegistry() {
|
|
|
7204
7645
|
return {};
|
|
7205
7646
|
}
|
|
7206
7647
|
},
|
|
7648
|
+
{
|
|
7649
|
+
name: "session.deliver",
|
|
7650
|
+
async handle(payload, ctx) {
|
|
7651
|
+
const taskId = requireString(payload, "taskId");
|
|
7652
|
+
const text = requireString(payload, "text");
|
|
7653
|
+
const source = optionalString(payload, "source");
|
|
7654
|
+
if (source !== undefined && source !== "note" && source !== "dispatcher") {
|
|
7655
|
+
throw new Error('source must be "note" or "dispatcher"');
|
|
7656
|
+
}
|
|
7657
|
+
if (!ctx.orch.getTask(taskId))
|
|
7658
|
+
throw new Error(`task not found: ${taskId}`);
|
|
7659
|
+
ctx.bus.publish("session.deliver", {
|
|
7660
|
+
taskId,
|
|
7661
|
+
text,
|
|
7662
|
+
at: Date.now(),
|
|
7663
|
+
source: source ?? "dispatcher"
|
|
7664
|
+
});
|
|
7665
|
+
return { ok: true };
|
|
7666
|
+
}
|
|
7667
|
+
},
|
|
7668
|
+
{
|
|
7669
|
+
name: "note.file",
|
|
7670
|
+
async handle(payload, ctx) {
|
|
7671
|
+
const taskId = requireString(payload, "taskId");
|
|
7672
|
+
const text = requireString(payload, "text");
|
|
7673
|
+
const author = ctx.orch.getTask(taskId);
|
|
7674
|
+
if (!author)
|
|
7675
|
+
throw new Error(`task not found: ${taskId}`);
|
|
7676
|
+
const main = ctx.orch.listTasks().find((t) => (t.kind ?? "task") === "main" && t.repo === author.repo && !t.archived);
|
|
7677
|
+
if (!main || main.id === author.id)
|
|
7678
|
+
return { ok: true, routed: false };
|
|
7679
|
+
const label = author.title || author.branch || taskId;
|
|
7680
|
+
ctx.bus.publish("session.deliver", {
|
|
7681
|
+
taskId: main.id,
|
|
7682
|
+
text: `[KOBE FIELD NOTE] from "${label}" (task ${taskId}): ${text}`,
|
|
7683
|
+
at: Date.now(),
|
|
7684
|
+
source: "note"
|
|
7685
|
+
});
|
|
7686
|
+
return { ok: true, routed: true };
|
|
7687
|
+
}
|
|
7688
|
+
},
|
|
7207
7689
|
{
|
|
7208
7690
|
name: "engine.reportEvent",
|
|
7209
7691
|
async handle(payload, ctx) {
|
|
@@ -7227,6 +7709,13 @@ function createDaemonHandlerRegistry() {
|
|
|
7227
7709
|
return {};
|
|
7228
7710
|
const detail = optionalActivityDetail(payload);
|
|
7229
7711
|
ctx.activity.report(taskId, kind, detail);
|
|
7712
|
+
if (kind === "turn-start") {
|
|
7713
|
+
maybeAutoStart(ctx.orch, taskId).then((result) => {
|
|
7714
|
+
if (result === "moved") {
|
|
7715
|
+
console.log(`[status-rules] task ${taskId} auto-moved backlog \u2192 in_progress`);
|
|
7716
|
+
}
|
|
7717
|
+
}).catch((err) => logDaemonError("status-rules", err));
|
|
7718
|
+
}
|
|
7230
7719
|
return {};
|
|
7231
7720
|
}
|
|
7232
7721
|
}
|
|
@@ -7280,6 +7769,7 @@ function optionalActivityDetail(payload) {
|
|
|
7280
7769
|
}
|
|
7281
7770
|
var init_handlers = __esm(() => {
|
|
7282
7771
|
init_hook_events();
|
|
7772
|
+
init_status_rules();
|
|
7283
7773
|
init_version();
|
|
7284
7774
|
init_cwd_task();
|
|
7285
7775
|
init_protocol();
|
|
@@ -7414,65 +7904,6 @@ var init_ui_prefs_watcher = __esm(() => {
|
|
|
7414
7904
|
FOCUS_ACCENT_SLOT_NAMES = ["primary", "success", "info"];
|
|
7415
7905
|
});
|
|
7416
7906
|
|
|
7417
|
-
// src/lib/poll-scheduling.ts
|
|
7418
|
-
import { spawn as spawn2 } from "child_process";
|
|
7419
|
-
function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
|
|
7420
|
-
if (timedOut)
|
|
7421
|
-
return startedAt + cfg.slowRetryMs;
|
|
7422
|
-
return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
|
|
7423
|
-
}
|
|
7424
|
-
function shouldPoll(state, now) {
|
|
7425
|
-
return !state.inFlight && now >= state.nextAllowedAt;
|
|
7426
|
-
}
|
|
7427
|
-
function maybeStartScheduledRun(state, cfg, run, onValue) {
|
|
7428
|
-
const startedAt = Date.now();
|
|
7429
|
-
if (!shouldPoll(state, startedAt))
|
|
7430
|
-
return false;
|
|
7431
|
-
state.inFlight = true;
|
|
7432
|
-
const controller = new AbortController;
|
|
7433
|
-
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
7434
|
-
(async () => {
|
|
7435
|
-
let value;
|
|
7436
|
-
let ok = false;
|
|
7437
|
-
try {
|
|
7438
|
-
value = await run(controller.signal);
|
|
7439
|
-
ok = true;
|
|
7440
|
-
} catch {}
|
|
7441
|
-
clearTimeout(timer);
|
|
7442
|
-
const timedOut = controller.signal.aborted;
|
|
7443
|
-
state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
|
|
7444
|
-
state.inFlight = false;
|
|
7445
|
-
if (ok && !timedOut)
|
|
7446
|
-
onValue(value);
|
|
7447
|
-
})();
|
|
7448
|
-
return true;
|
|
7449
|
-
}
|
|
7450
|
-
function spawnCapture(cmd, args, opts) {
|
|
7451
|
-
return new Promise((resolve2) => {
|
|
7452
|
-
let out = "";
|
|
7453
|
-
let settled = false;
|
|
7454
|
-
const finish = (status) => {
|
|
7455
|
-
if (settled)
|
|
7456
|
-
return;
|
|
7457
|
-
settled = true;
|
|
7458
|
-
resolve2({ status, stdout: out });
|
|
7459
|
-
};
|
|
7460
|
-
const child = spawn2(cmd, args.slice(), {
|
|
7461
|
-
cwd: opts.cwd,
|
|
7462
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
7463
|
-
env: opts.env,
|
|
7464
|
-
signal: opts.signal,
|
|
7465
|
-
killSignal: "SIGKILL"
|
|
7466
|
-
});
|
|
7467
|
-
child.stdout?.on("data", (chunk) => {
|
|
7468
|
-
out += String(chunk);
|
|
7469
|
-
});
|
|
7470
|
-
child.on("error", () => finish(null));
|
|
7471
|
-
child.on("close", (code) => finish(code));
|
|
7472
|
-
});
|
|
7473
|
-
}
|
|
7474
|
-
var init_poll_scheduling = () => {};
|
|
7475
|
-
|
|
7476
7907
|
// src/tui/panes/sidebar/worktree-changes.ts
|
|
7477
7908
|
var exports_worktree_changes = {};
|
|
7478
7909
|
__export(exports_worktree_changes, {
|
|
@@ -7753,6 +8184,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7753
8184
|
debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
|
|
7754
8185
|
});
|
|
7755
8186
|
const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS, hasSubscribers);
|
|
8187
|
+
const stopConflictCollector = startConflictCollector(orch, bus, options.conflictsTickMs ?? DEFAULT_CONFLICTS_TICK_MS, hasSubscribers);
|
|
7756
8188
|
const serverApi = {
|
|
7757
8189
|
socketPath,
|
|
7758
8190
|
pidPath,
|
|
@@ -7768,6 +8200,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7768
8200
|
stopUiPrefsWatcher();
|
|
7769
8201
|
stopKeybindingsWatcher();
|
|
7770
8202
|
stopWorktreeChangesCollector();
|
|
8203
|
+
stopConflictCollector();
|
|
7771
8204
|
activity.close();
|
|
7772
8205
|
broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
|
|
7773
8206
|
for (const client of Array.from(clients)) {
|
|
@@ -7898,6 +8331,7 @@ var init_server = __esm(() => {
|
|
|
7898
8331
|
init_version();
|
|
7899
8332
|
init_activity_registry();
|
|
7900
8333
|
init_auto_title_poller();
|
|
8334
|
+
init_conflict_collector();
|
|
7901
8335
|
init_handlers();
|
|
7902
8336
|
init_keybindings_watcher();
|
|
7903
8337
|
init_paths2();
|
|
@@ -8218,6 +8652,15 @@ var init_repo_cmd = __esm(() => {
|
|
|
8218
8652
|
`);
|
|
8219
8653
|
});
|
|
8220
8654
|
|
|
8655
|
+
// src/state/dispatcher.ts
|
|
8656
|
+
function dispatcherEnabled() {
|
|
8657
|
+
return loadStateFile()[DISPATCHER_KEY] === true;
|
|
8658
|
+
}
|
|
8659
|
+
var DISPATCHER_KEY = "experimental.dispatcher";
|
|
8660
|
+
var init_dispatcher = __esm(() => {
|
|
8661
|
+
init_store();
|
|
8662
|
+
});
|
|
8663
|
+
|
|
8221
8664
|
// src/engine/interactive-command.ts
|
|
8222
8665
|
import { randomUUID } from "crypto";
|
|
8223
8666
|
function engineCommandKey(vendor) {
|
|
@@ -8259,9 +8702,88 @@ function withClaudeSessionId(argv, vendor) {
|
|
|
8259
8702
|
const sessionId = randomUUID();
|
|
8260
8703
|
return { argv: [...argv, "--session-id", sessionId], sessionId };
|
|
8261
8704
|
}
|
|
8705
|
+
function kobeApiInvocation() {
|
|
8706
|
+
const quote = (a) => /^[A-Za-z0-9_/.:=-]+$/.test(a) ? a : `'${a.replace(/'/g, "'\\''")}'`;
|
|
8707
|
+
try {
|
|
8708
|
+
return [...kobeCliInvocation(), "api"].map(quote).join(" ");
|
|
8709
|
+
} catch {
|
|
8710
|
+
return "kobe api";
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
function statusReportProtocol(taskId, api = kobeApiInvocation()) {
|
|
8714
|
+
return [
|
|
8715
|
+
`You are running inside kobe (a local multi-session task manager) as task ${taskId}.`,
|
|
8716
|
+
"kobe tracks a lifecycle status for this task on a board.",
|
|
8717
|
+
"When you have COMPLETED the work requested in this session and verified it, report it by running:",
|
|
8718
|
+
` ${api} set-status --task-id ${taskId} --status in_review`,
|
|
8719
|
+
"Run it only when the work is genuinely done \u2014 never while you are asking the user a question, waiting for input, or mid-task.",
|
|
8720
|
+
"Never set any other status value; everything beyond in_review is the user's decision."
|
|
8721
|
+
].join(`
|
|
8722
|
+
`);
|
|
8723
|
+
}
|
|
8724
|
+
function noteFilingProtocol(taskId, api = kobeApiInvocation()) {
|
|
8725
|
+
return [
|
|
8726
|
+
"kobe shares hard-won discoveries between its parallel sessions as one-line field notes.",
|
|
8727
|
+
"When you RESOLVE a non-obvious, repo-level gotcha (a build flag, a flaky test, an environment quirk, an API trap), file it:",
|
|
8728
|
+
` ${api} note --task-id ${taskId} --text "<one line: the verified conclusion>"`,
|
|
8729
|
+
"File only verified conclusions another session could act on \u2014 never progress logs, opinions, or details specific to your own task. A handful per session at most."
|
|
8730
|
+
].join(`
|
|
8731
|
+
`);
|
|
8732
|
+
}
|
|
8733
|
+
function worktreeProtocol(taskId, api = kobeApiInvocation(), gates = {}) {
|
|
8734
|
+
const parts = [];
|
|
8735
|
+
if ((gates.status ?? autoStatusEnabled)())
|
|
8736
|
+
parts.push(statusReportProtocol(taskId, api));
|
|
8737
|
+
if ((gates.notes ?? dispatcherEnabled)())
|
|
8738
|
+
parts.push(noteFilingProtocol(taskId, api));
|
|
8739
|
+
return parts.length > 0 ? parts.join(`
|
|
8740
|
+
|
|
8741
|
+
`) : null;
|
|
8742
|
+
}
|
|
8743
|
+
function withWorktreeProtocol(argv, vendor, taskId, gates = {}) {
|
|
8744
|
+
if (!taskId)
|
|
8745
|
+
return argv;
|
|
8746
|
+
if ((vendor ?? "claude") !== "claude")
|
|
8747
|
+
return argv;
|
|
8748
|
+
if (argv.includes("--append-system-prompt") || argv.includes("--append-system-prompt-file")) {
|
|
8749
|
+
return argv;
|
|
8750
|
+
}
|
|
8751
|
+
const text = worktreeProtocol(taskId, kobeApiInvocation(), gates);
|
|
8752
|
+
if (!text)
|
|
8753
|
+
return argv;
|
|
8754
|
+
return [...argv, "--append-system-prompt", text];
|
|
8755
|
+
}
|
|
8756
|
+
function dispatcherProtocol(taskId, api = kobeApiInvocation()) {
|
|
8757
|
+
return [
|
|
8758
|
+
`You are running inside kobe (a local multi-session task manager) as this repository's DISPATCHER (task ${taskId}, the repo's main session).`,
|
|
8759
|
+
"kobe runs multiple worktree task sessions on this repo in parallel. When one of them resolves a non-obvious gotcha, it files a one-line field note; kobe forwards each note to you as a user message prefixed with [KOBE FIELD NOTE].",
|
|
8760
|
+
"Your job is routing that knowledge, fully autonomously \u2014 never ask the user for permission:",
|
|
8761
|
+
` - See the fleet: \`${api} collect --repo .\` (status, running, change counts per task), or \`--task-ids id1,id2\` for specific tasks.`,
|
|
8762
|
+
` - Relay a note to a task that would benefit: \`${api} dispatch --task-id <id> --prompt "[dispatcher] FYI from <author task>: <note verbatim>"\`.`,
|
|
8763
|
+
" - Relay to the in-flight tasks whose work plausibly touches the same area \u2014 and to nobody else. If no task benefits, do nothing.",
|
|
8764
|
+
" - Never relay a note back to its author, never relay the same note to the same task twice, and keep relays verbatim with provenance \u2014 no summarizing, no embellishment.",
|
|
8765
|
+
"Use ONLY the dispatch verb to message sessions \u2014 the `send` verb pastes via tmux and would spawn a DUPLICATE engine for web-hosted sessions. If dispatch fails, report the error in your own session and stop; do not fall back.",
|
|
8766
|
+
"Take no action on merge conflicts between tasks \u2014 the board's conflict radar is display-only by design, and resolution timing belongs to the humans and the tasks themselves.",
|
|
8767
|
+
"Never run git commands inside other tasks' worktrees."
|
|
8768
|
+
].join(`
|
|
8769
|
+
`);
|
|
8770
|
+
}
|
|
8771
|
+
function withDispatcherProtocol(argv, vendor, taskId, enabled = dispatcherEnabled) {
|
|
8772
|
+
if (!taskId || !enabled())
|
|
8773
|
+
return argv;
|
|
8774
|
+
if ((vendor ?? "claude") !== "claude")
|
|
8775
|
+
return argv;
|
|
8776
|
+
if (argv.includes("--append-system-prompt") || argv.includes("--append-system-prompt-file")) {
|
|
8777
|
+
return argv;
|
|
8778
|
+
}
|
|
8779
|
+
return [...argv, "--append-system-prompt", dispatcherProtocol(taskId)];
|
|
8780
|
+
}
|
|
8262
8781
|
var VENDOR_LABEL, CLAUDE_SESSION_CONTROL_FLAGS;
|
|
8263
8782
|
var init_interactive_command = __esm(() => {
|
|
8783
|
+
init_invocation();
|
|
8264
8784
|
init_registry();
|
|
8785
|
+
init_auto_status();
|
|
8786
|
+
init_dispatcher();
|
|
8265
8787
|
init_repos();
|
|
8266
8788
|
init_vendor();
|
|
8267
8789
|
VENDOR_LABEL = Object.fromEntries(BUILTIN_VENDORS.map((v) => [v, engineEntry(v).displayName]));
|
|
@@ -11062,7 +11584,11 @@ async function ensureSessionImpl(opts) {
|
|
|
11062
11584
|
}
|
|
11063
11585
|
const inv = kobeCliInvocation();
|
|
11064
11586
|
const launch = withClaudeSessionId(opts.command, opts.vendor);
|
|
11065
|
-
const
|
|
11587
|
+
const isMainSession = opts.repo !== undefined && opts.cwd === opts.repo;
|
|
11588
|
+
const protocolTaskId = isMainSession || remoteKey ? undefined : opts.taskId;
|
|
11589
|
+
const dispatcherTaskId = isMainSession && !remoteKey ? opts.taskId : undefined;
|
|
11590
|
+
const launchArgv = withDispatcherProtocol(withWorktreeProtocol(launch.argv, opts.vendor, protocolTaskId), opts.vendor, dispatcherTaskId);
|
|
11591
|
+
const engineCmd = wrapEngineLaunch(shellQuoteArgv(launchArgv), remoteKey, opts.cwd);
|
|
11066
11592
|
const r0 = await runTmuxCapturing([
|
|
11067
11593
|
"new-session",
|
|
11068
11594
|
"-d",
|
|
@@ -11688,6 +12214,19 @@ async function send(ctx) {
|
|
|
11688
12214
|
engineReady: delivered.engineReady
|
|
11689
12215
|
};
|
|
11690
12216
|
}
|
|
12217
|
+
async function dispatch(ctx) {
|
|
12218
|
+
const daemon = daemonOf(ctx);
|
|
12219
|
+
const taskId = ctx.args.require("task-id");
|
|
12220
|
+
const text = ctx.args.require("prompt");
|
|
12221
|
+
await daemon.request("session.deliver", { taskId, text, source: "dispatcher" });
|
|
12222
|
+
return { ok: true, taskId, routed: "session.deliver" };
|
|
12223
|
+
}
|
|
12224
|
+
async function note(ctx) {
|
|
12225
|
+
const daemon = daemonOf(ctx);
|
|
12226
|
+
const taskId = ctx.args.require("task-id");
|
|
12227
|
+
const text = ctx.args.require("text");
|
|
12228
|
+
return await daemon.request("note.file", { taskId, text });
|
|
12229
|
+
}
|
|
11691
12230
|
async function getTask(ctx) {
|
|
11692
12231
|
const daemon = daemonOf(ctx);
|
|
11693
12232
|
const taskId = ctx.args.require("task-id");
|
|
@@ -11936,7 +12475,7 @@ var init_api_cmd = __esm(() => {
|
|
|
11936
12475
|
discover: ["schema"],
|
|
11937
12476
|
read: ["list", "get-task", "collect"],
|
|
11938
12477
|
create: ["add", "fan-out"],
|
|
11939
|
-
drive: ["send", "set-active"],
|
|
12478
|
+
drive: ["send", "dispatch", "note", "set-active"],
|
|
11940
12479
|
edit: ["rename", "set-branch", "set-vendor", "set-status"],
|
|
11941
12480
|
lifecycle: ["archive", "pin", "delete"],
|
|
11942
12481
|
worktree: ["ensure-worktree", "adopt", "discover-adoptable"],
|
|
@@ -12016,6 +12555,27 @@ var init_api_cmd = __esm(() => {
|
|
|
12016
12555
|
flags: [F.taskId(false), F.prompt(true, "Text pasted + submitted into the engine pane.")],
|
|
12017
12556
|
handler: send
|
|
12018
12557
|
},
|
|
12558
|
+
{
|
|
12559
|
+
name: "dispatch",
|
|
12560
|
+
summary: "Route text into a task's live session via the daemon's session.deliver channel \u2014 the front-end hosting the session pastes it. The dispatcher's messenger (docs/design/dispatcher.md); unlike `send`, it never spawns or touches tmux itself.",
|
|
12561
|
+
flags: [F.taskId(true), F.prompt(true, "Text delivered into the task's engine session.")],
|
|
12562
|
+
handler: dispatch
|
|
12563
|
+
},
|
|
12564
|
+
{
|
|
12565
|
+
name: "note",
|
|
12566
|
+
summary: "File a one-line field note \u2014 a resolved, repo-level gotcha worth sharing. kobe forwards it to the repo's dispatcher session (the main session), which relays it to the in-flight tasks that benefit (docs/design/dispatcher.md).",
|
|
12567
|
+
flags: [
|
|
12568
|
+
F.taskId(true),
|
|
12569
|
+
{
|
|
12570
|
+
name: "text",
|
|
12571
|
+
type: "string",
|
|
12572
|
+
required: true,
|
|
12573
|
+
placeholder: "TEXT",
|
|
12574
|
+
description: "One line: the verified conclusion another session could act on."
|
|
12575
|
+
}
|
|
12576
|
+
],
|
|
12577
|
+
handler: note
|
|
12578
|
+
},
|
|
12019
12579
|
{
|
|
12020
12580
|
name: "feedback",
|
|
12021
12581
|
summary: "Create a GitHub Discussion in the kobe repo's Feedback category through `gh`.",
|
|
@@ -13470,6 +14030,8 @@ var init_spa_channels = __esm(() => {
|
|
|
13470
14030
|
"update",
|
|
13471
14031
|
"task.jobs",
|
|
13472
14032
|
"worktree.changes",
|
|
14033
|
+
"task.conflicts",
|
|
14034
|
+
"session.deliver",
|
|
13473
14035
|
"ui-prefs"
|
|
13474
14036
|
];
|
|
13475
14037
|
SPA_CHANNEL_SET = new Set(SPA_CHANNELS);
|
|
@@ -13493,6 +14055,8 @@ class DaemonLink {
|
|
|
13493
14055
|
update = null;
|
|
13494
14056
|
jobs = {};
|
|
13495
14057
|
worktreeChanges = {};
|
|
14058
|
+
conflicts = [];
|
|
14059
|
+
deliver = null;
|
|
13496
14060
|
uiPrefs = null;
|
|
13497
14061
|
async start() {
|
|
13498
14062
|
await this.connectOnce(true);
|
|
@@ -13505,6 +14069,8 @@ class DaemonLink {
|
|
|
13505
14069
|
update: this.update,
|
|
13506
14070
|
jobs: this.jobs,
|
|
13507
14071
|
worktreeChanges: this.worktreeChanges,
|
|
14072
|
+
conflicts: this.conflicts,
|
|
14073
|
+
deliver: this.deliver,
|
|
13508
14074
|
uiPrefs: this.uiPrefs,
|
|
13509
14075
|
connected: this.connected
|
|
13510
14076
|
};
|
|
@@ -13615,6 +14181,12 @@ class DaemonLink {
|
|
|
13615
14181
|
case "worktree.changes":
|
|
13616
14182
|
this.worktreeChanges = payload.changes;
|
|
13617
14183
|
break;
|
|
14184
|
+
case "task.conflicts":
|
|
14185
|
+
this.conflicts = payload.pairs;
|
|
14186
|
+
break;
|
|
14187
|
+
case "session.deliver":
|
|
14188
|
+
this.deliver = payload;
|
|
14189
|
+
break;
|
|
13618
14190
|
case "ui-prefs":
|
|
13619
14191
|
this.uiPrefs = payload;
|
|
13620
14192
|
break;
|
|
@@ -13662,6 +14234,7 @@ var init_rpc_allowlist = __esm(() => {
|
|
|
13662
14234
|
"task.pin",
|
|
13663
14235
|
"task.move",
|
|
13664
14236
|
"task.status",
|
|
14237
|
+
"task.reorder",
|
|
13665
14238
|
"task.ensureMain",
|
|
13666
14239
|
"task.ensureWorktree",
|
|
13667
14240
|
"task.setActive",
|
|
@@ -13708,7 +14281,11 @@ function shellQuote2(argv) {
|
|
|
13708
14281
|
}
|
|
13709
14282
|
async function engineSpec(link, taskId) {
|
|
13710
14283
|
const { task, worktreePath } = await ensureTaskWorktree(link, taskId);
|
|
13711
|
-
const
|
|
14284
|
+
const protocolTaskId = task.kind === "main" ? undefined : taskId;
|
|
14285
|
+
const dispatcherTaskId = task.kind === "main" ? taskId : undefined;
|
|
14286
|
+
const argv = [
|
|
14287
|
+
...withDispatcherProtocol(withWorktreeProtocol(interactiveEngineCommand(task.vendor), task.vendor, protocolTaskId), task.vendor, dispatcherTaskId)
|
|
14288
|
+
];
|
|
13712
14289
|
const init = resolveRepoInit(task.repo ?? "", worktreePath);
|
|
13713
14290
|
const quoted = shellQuote2(argv);
|
|
13714
14291
|
const script = init.initScript?.trim() ? `${init.initScript}
|
|
@@ -13790,7 +14367,8 @@ async function rpcResponse(req, link, tearDown) {
|
|
|
13790
14367
|
}
|
|
13791
14368
|
return Response.json({ result });
|
|
13792
14369
|
} catch (err) {
|
|
13793
|
-
|
|
14370
|
+
const name = err instanceof Error && err.name !== "Error" ? err.name : undefined;
|
|
14371
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err), ...name ? { name } : {} }, { status: 500 });
|
|
13794
14372
|
}
|
|
13795
14373
|
}
|
|
13796
14374
|
async function enginesResponse() {
|
|
@@ -13848,6 +14426,10 @@ function createRequestHandler(deps) {
|
|
|
13848
14426
|
return specResponse(url, link, terminalSpec);
|
|
13849
14427
|
if (url.pathname === "/api/engines" && req.method === "GET")
|
|
13850
14428
|
return enginesResponse();
|
|
14429
|
+
if (url.pathname === "/api/quick-prompts" && req.method === "GET")
|
|
14430
|
+
return quickPromptsGet();
|
|
14431
|
+
if (url.pathname === "/api/quick-prompts" && req.method === "PUT")
|
|
14432
|
+
return quickPromptsPut(req);
|
|
13851
14433
|
const notes = await handleNotesRequest(req, url);
|
|
13852
14434
|
if (notes)
|
|
13853
14435
|
return notes;
|
|
@@ -13865,6 +14447,24 @@ function createRequestHandler(deps) {
|
|
|
13865
14447
|
return new Response("not found", { status: 404 });
|
|
13866
14448
|
};
|
|
13867
14449
|
}
|
|
14450
|
+
function quickPromptsGet() {
|
|
14451
|
+
return Response.json({
|
|
14452
|
+
review: getPersistedString(QUICK_PROMPT_KEYS.review) ?? null,
|
|
14453
|
+
pr: getPersistedString(QUICK_PROMPT_KEYS.pr) ?? null
|
|
14454
|
+
});
|
|
14455
|
+
}
|
|
14456
|
+
async function quickPromptsPut(req) {
|
|
14457
|
+
try {
|
|
14458
|
+
const body = await req.json();
|
|
14459
|
+
if (typeof body.review === "string")
|
|
14460
|
+
setPersistedString(QUICK_PROMPT_KEYS.review, body.review);
|
|
14461
|
+
if (typeof body.pr === "string")
|
|
14462
|
+
setPersistedString(QUICK_PROMPT_KEYS.pr, body.pr);
|
|
14463
|
+
return quickPromptsGet();
|
|
14464
|
+
} catch (err) {
|
|
14465
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
|
|
14466
|
+
}
|
|
14467
|
+
}
|
|
13868
14468
|
async function staticResponse(pathname, staticDir) {
|
|
13869
14469
|
const rel = pathname === "/" ? "/index.html" : pathname;
|
|
13870
14470
|
const resolved = normalize2(join12(staticDir, rel));
|
|
@@ -13942,16 +14542,21 @@ async function createBridgeServer(opts = {}) {
|
|
|
13942
14542
|
}
|
|
13943
14543
|
};
|
|
13944
14544
|
}
|
|
13945
|
-
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web";
|
|
14545
|
+
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", QUICK_PROMPT_KEYS;
|
|
13946
14546
|
var init_bridge = __esm(() => {
|
|
13947
14547
|
init_account_detect();
|
|
13948
14548
|
init_interactive_command();
|
|
14549
|
+
init_repos();
|
|
13949
14550
|
init_history4();
|
|
13950
14551
|
init_notes();
|
|
13951
14552
|
init_themes();
|
|
13952
14553
|
init_daemon_link();
|
|
13953
14554
|
init_rpc_allowlist();
|
|
13954
14555
|
init_session();
|
|
14556
|
+
QUICK_PROMPT_KEYS = {
|
|
14557
|
+
review: "boardPrompt.review",
|
|
14558
|
+
pr: "boardPrompt.pr"
|
|
14559
|
+
};
|
|
13955
14560
|
});
|
|
13956
14561
|
|
|
13957
14562
|
// ../kobe-web/server/index.ts
|
|
@@ -17267,7 +17872,7 @@ function clampCursor(cursor, listLength) {
|
|
|
17267
17872
|
return 0;
|
|
17268
17873
|
return Math.max(0, Math.min(listLength - 1, cursor));
|
|
17269
17874
|
}
|
|
17270
|
-
function
|
|
17875
|
+
function resolveBaseRef2(typed, filteredBranches, cursor) {
|
|
17271
17876
|
const picked = filteredBranches[cursor];
|
|
17272
17877
|
if (picked)
|
|
17273
17878
|
return picked;
|
|
@@ -17841,7 +18446,7 @@ function NewTaskDialogView(props) {
|
|
|
17841
18446
|
setBaseRef(stripNewlines(v));
|
|
17842
18447
|
});
|
|
17843
18448
|
setProp(_el$28, "onSubmit", () => {
|
|
17844
|
-
setBaseRef(
|
|
18449
|
+
setBaseRef(resolveBaseRef2(baseRef(), branchFiltered(), branchCursor()));
|
|
17845
18450
|
setBaseRefTouched(true);
|
|
17846
18451
|
setField("confirm");
|
|
17847
18452
|
});
|
|
@@ -20661,7 +21266,9 @@ function devRows(hasDaemon) {
|
|
|
20661
21266
|
return [
|
|
20662
21267
|
{ id: "dev-reset", kind: "devReset" },
|
|
20663
21268
|
...hasDaemon ? [{ id: "dev-restart", kind: "devRestartDaemon" }] : [],
|
|
20664
|
-
{ id: "remote-projects", kind: "devRemoteProjects" }
|
|
21269
|
+
{ id: "remote-projects", kind: "devRemoteProjects" },
|
|
21270
|
+
{ id: "auto-status", kind: "devAutoStatus" },
|
|
21271
|
+
{ id: "dispatcher", kind: "devDispatcher" }
|
|
20665
21272
|
];
|
|
20666
21273
|
}
|
|
20667
21274
|
function sectionRows(section, input) {
|
|
@@ -21527,8 +22134,12 @@ function DevSettingsSection(props) {
|
|
|
21527
22134
|
const restartIsCursor = () => props.level() === "body" && props.bodyRow() === 1;
|
|
21528
22135
|
const experimentalRow = () => rowIndex(devRows(props.hasDaemon), "remote-projects");
|
|
21529
22136
|
const remoteIsCursor = () => props.level() === "body" && props.bodyRow() === experimentalRow();
|
|
22137
|
+
const autoStatusRow = () => rowIndex(devRows(props.hasDaemon), "auto-status");
|
|
22138
|
+
const autoStatusIsCursor = () => props.level() === "body" && props.bodyRow() === autoStatusRow();
|
|
22139
|
+
const dispatcherRow = () => rowIndex(devRows(props.hasDaemon), "dispatcher");
|
|
22140
|
+
const dispatcherIsCursor = () => props.level() === "body" && props.bodyRow() === dispatcherRow();
|
|
21530
22141
|
return (() => {
|
|
21531
|
-
var _el$128 = createElement("box"), _el$129 = createElement("text"), _el$131 = createElement("text"), _el$133 = createElement("box"), _el$134 = createElement("text"), _el$144 = createElement("text"), _el$146 = createElement("box"), _el$147 = createElement("text"), _el$149 = createElement("text"), _el$151 = createElement("box"), _el$152 = createElement("text");
|
|
22142
|
+
var _el$128 = createElement("box"), _el$129 = createElement("text"), _el$131 = createElement("text"), _el$133 = createElement("box"), _el$134 = createElement("text"), _el$144 = createElement("text"), _el$146 = createElement("box"), _el$147 = createElement("text"), _el$149 = createElement("text"), _el$151 = createElement("box"), _el$152 = createElement("text"), _el$153 = createElement("text"), _el$155 = createElement("box"), _el$156 = createElement("text"), _el$157 = createElement("text"), _el$159 = createElement("box"), _el$160 = createElement("text");
|
|
21532
22143
|
insertNode(_el$128, _el$129);
|
|
21533
22144
|
insertNode(_el$128, _el$131);
|
|
21534
22145
|
insertNode(_el$128, _el$133);
|
|
@@ -21599,6 +22210,10 @@ function DevSettingsSection(props) {
|
|
|
21599
22210
|
insertNode(_el$146, _el$147);
|
|
21600
22211
|
insertNode(_el$146, _el$149);
|
|
21601
22212
|
insertNode(_el$146, _el$151);
|
|
22213
|
+
insertNode(_el$146, _el$153);
|
|
22214
|
+
insertNode(_el$146, _el$155);
|
|
22215
|
+
insertNode(_el$146, _el$157);
|
|
22216
|
+
insertNode(_el$146, _el$159);
|
|
21602
22217
|
setProp(_el$146, "flexDirection", "column");
|
|
21603
22218
|
setProp(_el$146, "gap", 0);
|
|
21604
22219
|
setProp(_el$146, "paddingTop", 1);
|
|
@@ -21615,8 +22230,32 @@ function DevSettingsSection(props) {
|
|
|
21615
22230
|
props.toggleRemoteProjects();
|
|
21616
22231
|
});
|
|
21617
22232
|
insert(_el$152, () => props.remoteProjectsEnabled() ? "[x] Remote projects (on)" : "[ ] Remote projects (off)");
|
|
22233
|
+
insertNode(_el$153, createTextNode(`Auto status flow: a backlog task moves to in_progress when its engine starts a turn, and new claude sessions get a system-prompt note telling the agent to set in_review itself when the work is done. Never touches done/canceled.`));
|
|
22234
|
+
setProp(_el$153, "wrapMode", "word");
|
|
22235
|
+
insertNode(_el$155, _el$156);
|
|
22236
|
+
setProp(_el$155, "flexDirection", "row");
|
|
22237
|
+
setProp(_el$155, "paddingLeft", 1);
|
|
22238
|
+
setProp(_el$155, "paddingRight", 1);
|
|
22239
|
+
setProp(_el$155, "onMouseUp", () => {
|
|
22240
|
+
props.setLevel("body");
|
|
22241
|
+
props.setBodyRow(autoStatusRow());
|
|
22242
|
+
props.toggleAutoStatus();
|
|
22243
|
+
});
|
|
22244
|
+
insert(_el$156, () => props.autoStatusEnabled() ? "[x] Auto status flow (on)" : "[ ] Auto status flow (off)");
|
|
22245
|
+
insertNode(_el$157, createTextNode(`Field-notes dispatcher: task sessions file one-line gotchas (\`kobe api note\`), the daemon forwards each to the repo's main session, and that session relays them to the in-flight tasks that benefit (\`kobe api dispatch\`). Web-hosted sessions receive the relays today.`));
|
|
22246
|
+
setProp(_el$157, "wrapMode", "word");
|
|
22247
|
+
insertNode(_el$159, _el$160);
|
|
22248
|
+
setProp(_el$159, "flexDirection", "row");
|
|
22249
|
+
setProp(_el$159, "paddingLeft", 1);
|
|
22250
|
+
setProp(_el$159, "paddingRight", 1);
|
|
22251
|
+
setProp(_el$159, "onMouseUp", () => {
|
|
22252
|
+
props.setLevel("body");
|
|
22253
|
+
props.setBodyRow(dispatcherRow());
|
|
22254
|
+
props.toggleDispatcher();
|
|
22255
|
+
});
|
|
22256
|
+
insert(_el$160, () => props.dispatcherEnabled() ? "[x] Field-notes dispatcher (on)" : "[ ] Field-notes dispatcher (off)");
|
|
21618
22257
|
effect((_p$) => {
|
|
21619
|
-
var _v$86 = theme.text, _v$87 = TextAttributes7.BOLD, _v$88 = theme.textMuted, _v$89 = resetIsCursor() ? theme.primary : theme.backgroundElement, _v$90 = resetIsCursor() ? theme.selectedListItemText : theme.warning, _v$91 = TextAttributes7.BOLD, _v$92 = theme.textMuted, _v$93 = theme.text, _v$94 = TextAttributes7.BOLD, _v$95 = theme.textMuted, _v$96 = remoteIsCursor() ? theme.primary : theme.backgroundElement, _v$97 = remoteIsCursor() ? theme.selectedListItemText : theme.text, _v$98 = props.remoteProjectsEnabled() ? TextAttributes7.BOLD : undefined;
|
|
22258
|
+
var _v$86 = theme.text, _v$87 = TextAttributes7.BOLD, _v$88 = theme.textMuted, _v$89 = resetIsCursor() ? theme.primary : theme.backgroundElement, _v$90 = resetIsCursor() ? theme.selectedListItemText : theme.warning, _v$91 = TextAttributes7.BOLD, _v$92 = theme.textMuted, _v$93 = theme.text, _v$94 = TextAttributes7.BOLD, _v$95 = theme.textMuted, _v$96 = remoteIsCursor() ? theme.primary : theme.backgroundElement, _v$97 = remoteIsCursor() ? theme.selectedListItemText : theme.text, _v$98 = props.remoteProjectsEnabled() ? TextAttributes7.BOLD : undefined, _v$99 = theme.textMuted, _v$100 = autoStatusIsCursor() ? theme.primary : theme.backgroundElement, _v$101 = autoStatusIsCursor() ? theme.selectedListItemText : theme.text, _v$102 = props.autoStatusEnabled() ? TextAttributes7.BOLD : undefined, _v$103 = theme.textMuted, _v$104 = dispatcherIsCursor() ? theme.primary : theme.backgroundElement, _v$105 = dispatcherIsCursor() ? theme.selectedListItemText : theme.text, _v$106 = props.dispatcherEnabled() ? TextAttributes7.BOLD : undefined;
|
|
21620
22259
|
_v$86 !== _p$.e && (_p$.e = setProp(_el$129, "fg", _v$86, _p$.e));
|
|
21621
22260
|
_v$87 !== _p$.t && (_p$.t = setProp(_el$129, "attributes", _v$87, _p$.t));
|
|
21622
22261
|
_v$88 !== _p$.a && (_p$.a = setProp(_el$131, "fg", _v$88, _p$.a));
|
|
@@ -21630,6 +22269,14 @@ function DevSettingsSection(props) {
|
|
|
21630
22269
|
_v$96 !== _p$.l && (_p$.l = setProp(_el$151, "backgroundColor", _v$96, _p$.l));
|
|
21631
22270
|
_v$97 !== _p$.u && (_p$.u = setProp(_el$152, "fg", _v$97, _p$.u));
|
|
21632
22271
|
_v$98 !== _p$.c && (_p$.c = setProp(_el$152, "attributes", _v$98, _p$.c));
|
|
22272
|
+
_v$99 !== _p$.w && (_p$.w = setProp(_el$153, "fg", _v$99, _p$.w));
|
|
22273
|
+
_v$100 !== _p$.m && (_p$.m = setProp(_el$155, "backgroundColor", _v$100, _p$.m));
|
|
22274
|
+
_v$101 !== _p$.f && (_p$.f = setProp(_el$156, "fg", _v$101, _p$.f));
|
|
22275
|
+
_v$102 !== _p$.y && (_p$.y = setProp(_el$156, "attributes", _v$102, _p$.y));
|
|
22276
|
+
_v$103 !== _p$.g && (_p$.g = setProp(_el$157, "fg", _v$103, _p$.g));
|
|
22277
|
+
_v$104 !== _p$.p && (_p$.p = setProp(_el$159, "backgroundColor", _v$104, _p$.p));
|
|
22278
|
+
_v$105 !== _p$.b && (_p$.b = setProp(_el$160, "fg", _v$105, _p$.b));
|
|
22279
|
+
_v$106 !== _p$.T && (_p$.T = setProp(_el$160, "attributes", _v$106, _p$.T));
|
|
21633
22280
|
return _p$;
|
|
21634
22281
|
}, {
|
|
21635
22282
|
e: undefined,
|
|
@@ -21644,7 +22291,15 @@ function DevSettingsSection(props) {
|
|
|
21644
22291
|
d: undefined,
|
|
21645
22292
|
l: undefined,
|
|
21646
22293
|
u: undefined,
|
|
21647
|
-
c: undefined
|
|
22294
|
+
c: undefined,
|
|
22295
|
+
w: undefined,
|
|
22296
|
+
m: undefined,
|
|
22297
|
+
f: undefined,
|
|
22298
|
+
y: undefined,
|
|
22299
|
+
g: undefined,
|
|
22300
|
+
p: undefined,
|
|
22301
|
+
b: undefined,
|
|
22302
|
+
T: undefined
|
|
21648
22303
|
});
|
|
21649
22304
|
return _el$128;
|
|
21650
22305
|
})();
|
|
@@ -21656,63 +22311,63 @@ function KeybindingsSettingsSection() {
|
|
|
21656
22311
|
const report = userKeybindingsReport();
|
|
21657
22312
|
const fixedIds = Object.keys(FIXED_BINDING_IDS).sort();
|
|
21658
22313
|
return (() => {
|
|
21659
|
-
var _el$
|
|
21660
|
-
insertNode(_el$
|
|
21661
|
-
insertNode(_el$
|
|
21662
|
-
insertNode(_el$
|
|
21663
|
-
insertNode(_el$
|
|
21664
|
-
insertNode(_el$
|
|
21665
|
-
setProp(_el$
|
|
21666
|
-
setProp(_el$
|
|
21667
|
-
insertNode(_el$
|
|
21668
|
-
insertNode(_el$
|
|
21669
|
-
setProp(_el$
|
|
21670
|
-
insertNode(_el$
|
|
21671
|
-
insertNode(_el$
|
|
21672
|
-
setProp(_el$
|
|
21673
|
-
setProp(_el$
|
|
21674
|
-
insertNode(_el$
|
|
21675
|
-
setProp(_el$
|
|
21676
|
-
insert(_el$
|
|
21677
|
-
insert(_el$
|
|
21678
|
-
insert(_el$
|
|
22314
|
+
var _el$161 = createElement("box"), _el$162 = createElement("text"), _el$164 = createElement("text"), _el$166 = createElement("box"), _el$167 = createElement("text"), _el$169 = createElement("text"), _el$197 = createElement("text"), _el$199 = createElement("text");
|
|
22315
|
+
insertNode(_el$161, _el$162);
|
|
22316
|
+
insertNode(_el$161, _el$164);
|
|
22317
|
+
insertNode(_el$161, _el$166);
|
|
22318
|
+
insertNode(_el$161, _el$197);
|
|
22319
|
+
insertNode(_el$161, _el$199);
|
|
22320
|
+
setProp(_el$161, "flexDirection", "column");
|
|
22321
|
+
setProp(_el$161, "gap", 1);
|
|
22322
|
+
insertNode(_el$162, createTextNode(`Keybindings`));
|
|
22323
|
+
insertNode(_el$164, createTextNode(`Rebind chords by editing the YAML below, then restart kobe (or respawn the pane). Press F1 anywhere for the live keymap with every binding id.`));
|
|
22324
|
+
setProp(_el$164, "wrapMode", "word");
|
|
22325
|
+
insertNode(_el$166, _el$167);
|
|
22326
|
+
insertNode(_el$166, _el$169);
|
|
22327
|
+
setProp(_el$166, "flexDirection", "column");
|
|
22328
|
+
setProp(_el$166, "gap", 0);
|
|
22329
|
+
insertNode(_el$167, createTextNode(`Config file`));
|
|
22330
|
+
setProp(_el$169, "wrapMode", "word");
|
|
22331
|
+
insert(_el$169, () => report.path, null);
|
|
22332
|
+
insert(_el$169, () => report.exists ? "" : " (not created yet)", null);
|
|
22333
|
+
insert(_el$161, createComponent2(Show, {
|
|
21679
22334
|
get when() {
|
|
21680
22335
|
return !report.exists;
|
|
21681
22336
|
},
|
|
21682
22337
|
get children() {
|
|
21683
|
-
var _el$
|
|
21684
|
-
insertNode(_el$
|
|
21685
|
-
insertNode(_el$
|
|
21686
|
-
insertNode(_el$
|
|
21687
|
-
insertNode(_el$
|
|
21688
|
-
insertNode(_el$
|
|
21689
|
-
insertNode(_el$
|
|
21690
|
-
insertNode(_el$
|
|
21691
|
-
insertNode(_el$
|
|
21692
|
-
insertNode(_el$
|
|
21693
|
-
setProp(_el$
|
|
21694
|
-
setProp(_el$
|
|
21695
|
-
insertNode(_el$
|
|
21696
|
-
insertNode(_el$
|
|
21697
|
-
insertNode(_el$
|
|
21698
|
-
insertNode(_el$
|
|
21699
|
-
insertNode(_el$
|
|
21700
|
-
insertNode(_el$
|
|
21701
|
-
insertNode(_el$
|
|
21702
|
-
insertNode(_el$
|
|
21703
|
-
insertNode(_el$
|
|
22338
|
+
var _el$170 = createElement("box"), _el$171 = createElement("text"), _el$173 = createElement("text"), _el$175 = createElement("text"), _el$177 = createElement("text"), _el$179 = createElement("text"), _el$181 = createElement("text"), _el$183 = createElement("text"), _el$185 = createElement("text"), _el$187 = createElement("text");
|
|
22339
|
+
insertNode(_el$170, _el$171);
|
|
22340
|
+
insertNode(_el$170, _el$173);
|
|
22341
|
+
insertNode(_el$170, _el$175);
|
|
22342
|
+
insertNode(_el$170, _el$177);
|
|
22343
|
+
insertNode(_el$170, _el$179);
|
|
22344
|
+
insertNode(_el$170, _el$181);
|
|
22345
|
+
insertNode(_el$170, _el$183);
|
|
22346
|
+
insertNode(_el$170, _el$185);
|
|
22347
|
+
insertNode(_el$170, _el$187);
|
|
22348
|
+
setProp(_el$170, "flexDirection", "column");
|
|
22349
|
+
setProp(_el$170, "gap", 0);
|
|
22350
|
+
insertNode(_el$171, createTextNode(`Example`));
|
|
22351
|
+
insertNode(_el$173, createTextNode(`bindings:`));
|
|
22352
|
+
insertNode(_el$175, createTextNode(` chat.fork.new: ctrl+g # string = one chord`));
|
|
22353
|
+
insertNode(_el$177, createTextNode(` sidebar.select: [enter] # list = several chords`));
|
|
22354
|
+
insertNode(_el$179, createTextNode(` files.createPR: null # null = unbind`));
|
|
22355
|
+
insertNode(_el$181, createTextNode(` tmux.tab.new: ctrl+y # tmux session key (see below)`));
|
|
22356
|
+
insertNode(_el$183, createTextNode(`darwin: # platform overlay (also: linux)`));
|
|
22357
|
+
insertNode(_el$185, createTextNode(` bindings:`));
|
|
22358
|
+
insertNode(_el$187, createTextNode(` palette.open: [cmd+p, ctrl+p]`));
|
|
21704
22359
|
effect((_p$) => {
|
|
21705
|
-
var _v$
|
|
21706
|
-
_v$
|
|
21707
|
-
_v$
|
|
21708
|
-
_v$
|
|
21709
|
-
_v$
|
|
21710
|
-
_v$
|
|
21711
|
-
_v$
|
|
21712
|
-
_v$
|
|
21713
|
-
_v$
|
|
21714
|
-
_v$
|
|
21715
|
-
_v$
|
|
22360
|
+
var _v$107 = theme.text, _v$108 = TextAttributes7.BOLD, _v$109 = theme.textMuted, _v$110 = theme.textMuted, _v$111 = theme.textMuted, _v$112 = theme.textMuted, _v$113 = theme.textMuted, _v$114 = theme.textMuted, _v$115 = theme.textMuted, _v$116 = theme.textMuted;
|
|
22361
|
+
_v$107 !== _p$.e && (_p$.e = setProp(_el$171, "fg", _v$107, _p$.e));
|
|
22362
|
+
_v$108 !== _p$.t && (_p$.t = setProp(_el$171, "attributes", _v$108, _p$.t));
|
|
22363
|
+
_v$109 !== _p$.a && (_p$.a = setProp(_el$173, "fg", _v$109, _p$.a));
|
|
22364
|
+
_v$110 !== _p$.o && (_p$.o = setProp(_el$175, "fg", _v$110, _p$.o));
|
|
22365
|
+
_v$111 !== _p$.i && (_p$.i = setProp(_el$177, "fg", _v$111, _p$.i));
|
|
22366
|
+
_v$112 !== _p$.n && (_p$.n = setProp(_el$179, "fg", _v$112, _p$.n));
|
|
22367
|
+
_v$113 !== _p$.s && (_p$.s = setProp(_el$181, "fg", _v$113, _p$.s));
|
|
22368
|
+
_v$114 !== _p$.h && (_p$.h = setProp(_el$183, "fg", _v$114, _p$.h));
|
|
22369
|
+
_v$115 !== _p$.r && (_p$.r = setProp(_el$185, "fg", _v$115, _p$.r));
|
|
22370
|
+
_v$116 !== _p$.d && (_p$.d = setProp(_el$187, "fg", _v$116, _p$.d));
|
|
21716
22371
|
return _p$;
|
|
21717
22372
|
}, {
|
|
21718
22373
|
e: undefined,
|
|
@@ -21726,102 +22381,102 @@ function KeybindingsSettingsSection() {
|
|
|
21726
22381
|
r: undefined,
|
|
21727
22382
|
d: undefined
|
|
21728
22383
|
});
|
|
21729
|
-
return _el$
|
|
22384
|
+
return _el$170;
|
|
21730
22385
|
}
|
|
21731
|
-
}), _el$
|
|
21732
|
-
insert(_el$
|
|
22386
|
+
}), _el$197);
|
|
22387
|
+
insert(_el$161, createComponent2(Show, {
|
|
21733
22388
|
get when() {
|
|
21734
22389
|
return report.exists;
|
|
21735
22390
|
},
|
|
21736
22391
|
get children() {
|
|
21737
|
-
var _el$
|
|
21738
|
-
insertNode(_el$
|
|
21739
|
-
setProp(_el$
|
|
21740
|
-
setProp(_el$
|
|
21741
|
-
insertNode(_el$
|
|
21742
|
-
insert(_el$
|
|
22392
|
+
var _el$189 = createElement("box"), _el$190 = createElement("text");
|
|
22393
|
+
insertNode(_el$189, _el$190);
|
|
22394
|
+
setProp(_el$189, "flexDirection", "column");
|
|
22395
|
+
setProp(_el$189, "gap", 0);
|
|
22396
|
+
insertNode(_el$190, createTextNode(`Overrides applied`));
|
|
22397
|
+
insert(_el$189, createComponent2(Show, {
|
|
21743
22398
|
get when() {
|
|
21744
22399
|
return report.applied.length === 0;
|
|
21745
22400
|
},
|
|
21746
22401
|
get children() {
|
|
21747
|
-
var _el$
|
|
21748
|
-
insertNode(_el$
|
|
21749
|
-
effect((_$p) => setProp(_el$
|
|
21750
|
-
return _el$
|
|
22402
|
+
var _el$192 = createElement("text");
|
|
22403
|
+
insertNode(_el$192, createTextNode(`none`));
|
|
22404
|
+
effect((_$p) => setProp(_el$192, "fg", theme.textMuted, _$p));
|
|
22405
|
+
return _el$192;
|
|
21751
22406
|
}
|
|
21752
22407
|
}), null);
|
|
21753
|
-
insert(_el$
|
|
22408
|
+
insert(_el$189, createComponent2(For, {
|
|
21754
22409
|
get each() {
|
|
21755
22410
|
return report.applied;
|
|
21756
22411
|
},
|
|
21757
22412
|
children: (o) => (() => {
|
|
21758
|
-
var _el$
|
|
21759
|
-
setProp(_el$
|
|
21760
|
-
insert(_el$
|
|
21761
|
-
effect((_$p) => setProp(_el$
|
|
21762
|
-
return _el$
|
|
22413
|
+
var _el$200 = createElement("text");
|
|
22414
|
+
setProp(_el$200, "wrapMode", "word");
|
|
22415
|
+
insert(_el$200, () => `${o.id} \u2192 ${o.keys.length > 0 ? o.keys.join(" / ") : "(unbound)"} (default: ${o.defaultKeys.join(" / ")})`);
|
|
22416
|
+
effect((_$p) => setProp(_el$200, "fg", theme.text, _$p));
|
|
22417
|
+
return _el$200;
|
|
21763
22418
|
})()
|
|
21764
22419
|
}), null);
|
|
21765
22420
|
effect((_p$) => {
|
|
21766
|
-
var _v$
|
|
21767
|
-
_v$
|
|
21768
|
-
_v$
|
|
22421
|
+
var _v$117 = theme.text, _v$118 = TextAttributes7.BOLD;
|
|
22422
|
+
_v$117 !== _p$.e && (_p$.e = setProp(_el$190, "fg", _v$117, _p$.e));
|
|
22423
|
+
_v$118 !== _p$.t && (_p$.t = setProp(_el$190, "attributes", _v$118, _p$.t));
|
|
21769
22424
|
return _p$;
|
|
21770
22425
|
}, {
|
|
21771
22426
|
e: undefined,
|
|
21772
22427
|
t: undefined
|
|
21773
22428
|
});
|
|
21774
|
-
return _el$
|
|
22429
|
+
return _el$189;
|
|
21775
22430
|
}
|
|
21776
|
-
}), _el$
|
|
21777
|
-
insert(_el$
|
|
22431
|
+
}), _el$197);
|
|
22432
|
+
insert(_el$161, createComponent2(Show, {
|
|
21778
22433
|
get when() {
|
|
21779
22434
|
return report.warnings.length > 0;
|
|
21780
22435
|
},
|
|
21781
22436
|
get children() {
|
|
21782
|
-
var _el$
|
|
21783
|
-
insertNode(_el$
|
|
21784
|
-
setProp(_el$
|
|
21785
|
-
setProp(_el$
|
|
21786
|
-
insertNode(_el$
|
|
21787
|
-
insert(_el$
|
|
22437
|
+
var _el$194 = createElement("box"), _el$195 = createElement("text");
|
|
22438
|
+
insertNode(_el$194, _el$195);
|
|
22439
|
+
setProp(_el$194, "flexDirection", "column");
|
|
22440
|
+
setProp(_el$194, "gap", 0);
|
|
22441
|
+
insertNode(_el$195, createTextNode(`Warnings`));
|
|
22442
|
+
insert(_el$194, createComponent2(For, {
|
|
21788
22443
|
get each() {
|
|
21789
22444
|
return report.warnings;
|
|
21790
22445
|
},
|
|
21791
22446
|
children: (w) => (() => {
|
|
21792
|
-
var _el$
|
|
21793
|
-
setProp(_el$
|
|
21794
|
-
insert(_el$
|
|
21795
|
-
effect((_$p) => setProp(_el$
|
|
21796
|
-
return _el$
|
|
22447
|
+
var _el$201 = createElement("text");
|
|
22448
|
+
setProp(_el$201, "wrapMode", "word");
|
|
22449
|
+
insert(_el$201, `! ${w}`);
|
|
22450
|
+
effect((_$p) => setProp(_el$201, "fg", theme.warning, _$p));
|
|
22451
|
+
return _el$201;
|
|
21797
22452
|
})()
|
|
21798
22453
|
}), null);
|
|
21799
22454
|
effect((_p$) => {
|
|
21800
|
-
var _v$
|
|
21801
|
-
_v$
|
|
21802
|
-
_v$
|
|
22455
|
+
var _v$119 = theme.warning, _v$120 = TextAttributes7.BOLD;
|
|
22456
|
+
_v$119 !== _p$.e && (_p$.e = setProp(_el$195, "fg", _v$119, _p$.e));
|
|
22457
|
+
_v$120 !== _p$.t && (_p$.t = setProp(_el$195, "attributes", _v$120, _p$.t));
|
|
21803
22458
|
return _p$;
|
|
21804
22459
|
}, {
|
|
21805
22460
|
e: undefined,
|
|
21806
22461
|
t: undefined
|
|
21807
22462
|
});
|
|
21808
|
-
return _el$
|
|
22463
|
+
return _el$194;
|
|
21809
22464
|
}
|
|
21810
|
-
}), _el$
|
|
21811
|
-
insertNode(_el$
|
|
21812
|
-
setProp(_el$
|
|
21813
|
-
setProp(_el$
|
|
21814
|
-
insert(_el$
|
|
22465
|
+
}), _el$197);
|
|
22466
|
+
insertNode(_el$197, createTextNode(`tmux session keys use the same file: tmux.tab.new (ctrl+t), tmux.tab.prev/next (ctrl+[/]), tmux.tab.close (ctrl+w), tmux.tab.rename (f2), tmux.tab.chooseEngine (ctrl+shift+t), tmux.detach (ctrl+q), tmux.focus (4 chords, left/down/up/right). They apply when a session is (re)built.`));
|
|
22467
|
+
setProp(_el$197, "wrapMode", "word");
|
|
22468
|
+
setProp(_el$199, "wrapMode", "word");
|
|
22469
|
+
insert(_el$199, () => `Fixed (not rebindable): ${fixedIds.join(", ")}.`);
|
|
21815
22470
|
effect((_p$) => {
|
|
21816
|
-
var _v$
|
|
21817
|
-
_v$
|
|
21818
|
-
_v$
|
|
21819
|
-
_v$
|
|
21820
|
-
_v$
|
|
21821
|
-
_v$
|
|
21822
|
-
_v$
|
|
21823
|
-
_v$
|
|
21824
|
-
_v$
|
|
22471
|
+
var _v$121 = theme.text, _v$122 = TextAttributes7.BOLD, _v$123 = theme.textMuted, _v$124 = theme.text, _v$125 = TextAttributes7.BOLD, _v$126 = theme.textMuted, _v$127 = theme.textMuted, _v$128 = theme.textMuted;
|
|
22472
|
+
_v$121 !== _p$.e && (_p$.e = setProp(_el$162, "fg", _v$121, _p$.e));
|
|
22473
|
+
_v$122 !== _p$.t && (_p$.t = setProp(_el$162, "attributes", _v$122, _p$.t));
|
|
22474
|
+
_v$123 !== _p$.a && (_p$.a = setProp(_el$164, "fg", _v$123, _p$.a));
|
|
22475
|
+
_v$124 !== _p$.o && (_p$.o = setProp(_el$167, "fg", _v$124, _p$.o));
|
|
22476
|
+
_v$125 !== _p$.i && (_p$.i = setProp(_el$167, "attributes", _v$125, _p$.i));
|
|
22477
|
+
_v$126 !== _p$.n && (_p$.n = setProp(_el$169, "fg", _v$126, _p$.n));
|
|
22478
|
+
_v$127 !== _p$.s && (_p$.s = setProp(_el$197, "fg", _v$127, _p$.s));
|
|
22479
|
+
_v$128 !== _p$.h && (_p$.h = setProp(_el$199, "fg", _v$128, _p$.h));
|
|
21825
22480
|
return _p$;
|
|
21826
22481
|
}, {
|
|
21827
22482
|
e: undefined,
|
|
@@ -21833,7 +22488,7 @@ function KeybindingsSettingsSection() {
|
|
|
21833
22488
|
s: undefined,
|
|
21834
22489
|
h: undefined
|
|
21835
22490
|
});
|
|
21836
|
-
return _el$
|
|
22491
|
+
return _el$161;
|
|
21837
22492
|
})();
|
|
21838
22493
|
}
|
|
21839
22494
|
var init_sections = __esm(() => {
|
|
@@ -21946,6 +22601,18 @@ function SettingsDialog(props) {
|
|
|
21946
22601
|
function toggleRemoteProjects() {
|
|
21947
22602
|
props.kv.set("experimental.remoteProjects", !remoteProjectsEnabled());
|
|
21948
22603
|
}
|
|
22604
|
+
function autoStatusOn() {
|
|
22605
|
+
return props.kv.get(AUTO_STATUS_KEY, false) === true;
|
|
22606
|
+
}
|
|
22607
|
+
function toggleAutoStatus() {
|
|
22608
|
+
props.kv.set(AUTO_STATUS_KEY, !autoStatusOn());
|
|
22609
|
+
}
|
|
22610
|
+
function dispatcherOn() {
|
|
22611
|
+
return props.kv.get(DISPATCHER_KEY, false) === true;
|
|
22612
|
+
}
|
|
22613
|
+
function toggleDispatcher() {
|
|
22614
|
+
props.kv.set(DISPATCHER_KEY, !dispatcherOn());
|
|
22615
|
+
}
|
|
21949
22616
|
function customEngines() {
|
|
21950
22617
|
const raw = props.kv.get("customEngineIds", []);
|
|
21951
22618
|
return Array.isArray(raw) ? raw.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
|
|
@@ -22163,7 +22830,9 @@ function SettingsDialog(props) {
|
|
|
22163
22830
|
feedbackSend: () => void sendFeedback(),
|
|
22164
22831
|
devReset: () => void confirmResetState(dialog, props.kv, renderer),
|
|
22165
22832
|
devRestartDaemon: () => void confirmRestartDaemon(dialog, props.orchestrator, renderer),
|
|
22166
|
-
devRemoteProjects: () => toggleRemoteProjects()
|
|
22833
|
+
devRemoteProjects: () => toggleRemoteProjects(),
|
|
22834
|
+
devAutoStatus: () => toggleAutoStatus(),
|
|
22835
|
+
devDispatcher: () => toggleDispatcher()
|
|
22167
22836
|
};
|
|
22168
22837
|
function activateBodyRow() {
|
|
22169
22838
|
const row = rowAt(bodyRows(), bodyRow());
|
|
@@ -22369,7 +23038,11 @@ function SettingsDialog(props) {
|
|
|
22369
23038
|
confirmReset: () => void confirmResetState(dialog, props.kv, renderer),
|
|
22370
23039
|
confirmRestartDaemon: () => void confirmRestartDaemon(dialog, props.orchestrator, renderer),
|
|
22371
23040
|
remoteProjectsEnabled,
|
|
22372
|
-
toggleRemoteProjects
|
|
23041
|
+
toggleRemoteProjects,
|
|
23042
|
+
autoStatusEnabled: autoStatusOn,
|
|
23043
|
+
toggleAutoStatus,
|
|
23044
|
+
dispatcherEnabled: dispatcherOn,
|
|
23045
|
+
toggleDispatcher
|
|
22373
23046
|
});
|
|
22374
23047
|
}
|
|
22375
23048
|
}), null);
|
|
@@ -22405,6 +23078,8 @@ var init_settings_dialog = __esm(() => {
|
|
|
22405
23078
|
init_account_detect();
|
|
22406
23079
|
init_interactive_command();
|
|
22407
23080
|
init_feedback();
|
|
23081
|
+
init_auto_status();
|
|
23082
|
+
init_dispatcher();
|
|
22408
23083
|
init_repos();
|
|
22409
23084
|
init_vendor();
|
|
22410
23085
|
init_theme2();
|
|
@@ -26800,7 +27475,7 @@ var init_filetree = __esm(() => {
|
|
|
26800
27475
|
// src/tui/ops/pr-prompt.ts
|
|
26801
27476
|
import { promises as fs6 } from "fs";
|
|
26802
27477
|
import path12 from "path";
|
|
26803
|
-
async function
|
|
27478
|
+
async function git2(cwd, args2) {
|
|
26804
27479
|
const controller = new AbortController;
|
|
26805
27480
|
const timer = setTimeout(() => controller.abort(), GIT_TIMEOUT_MS2);
|
|
26806
27481
|
try {
|
|
@@ -26819,20 +27494,20 @@ async function git(cwd, args2) {
|
|
|
26819
27494
|
}
|
|
26820
27495
|
}
|
|
26821
27496
|
async function currentBranch2(cwd) {
|
|
26822
|
-
return await
|
|
27497
|
+
return await git2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) || "HEAD";
|
|
26823
27498
|
}
|
|
26824
27499
|
async function targetBranch(cwd) {
|
|
26825
|
-
const out = await
|
|
27500
|
+
const out = await git2(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
|
|
26826
27501
|
if (!out)
|
|
26827
27502
|
return "main";
|
|
26828
27503
|
return out.startsWith("origin/") ? out.slice("origin/".length) : out;
|
|
26829
27504
|
}
|
|
26830
27505
|
async function hasUpstream(cwd) {
|
|
26831
|
-
const out = await
|
|
27506
|
+
const out = await git2(cwd, ["rev-parse", "--abbrev-ref", "@{u}"]);
|
|
26832
27507
|
return out !== null && out.length > 0;
|
|
26833
27508
|
}
|
|
26834
27509
|
async function dirtyCount(cwd) {
|
|
26835
|
-
const out = await
|
|
27510
|
+
const out = await git2(cwd, ["status", "--porcelain"]);
|
|
26836
27511
|
if (!out)
|
|
26837
27512
|
return 0;
|
|
26838
27513
|
return out.split(`
|