@kw-yeh/vibeflow 4.5.2 → 4.6.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/dist/vibeflow.mjs +1040 -317
- package/dist/web/404/index.html +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/{5P4nrPXSK7r5MwLNDxF-J → 67i9UjSLB0D3UHnpZ00eV}/_buildManifest.js +1 -1
- package/dist/web/_next/static/chunks/8527-5442098d5121c96b.js +9 -0
- package/dist/web/_next/static/chunks/pages/{_app-2a4ae532bdcab6f2.js → _app-d965862fbafe3116.js} +1 -1
- package/dist/web/_next/static/chunks/pages/home-f31c4cf97ed808cf.js +12 -0
- package/dist/web/_next/static/css/7b78ec950bc90c91.css +2 -0
- package/dist/web/home/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/2524-abaf3a55023f6066.js +0 -9
- package/dist/web/_next/static/chunks/pages/home-a511856e480ddb33.js +0 -12
- package/dist/web/_next/static/css/24b804d01aa3af3c.css +0 -2
- /package/dist/web/_next/static/{5P4nrPXSK7r5MwLNDxF-J → 67i9UjSLB0D3UHnpZ00eV}/_ssgManifest.js +0 -0
package/dist/vibeflow.mjs
CHANGED
|
@@ -599,11 +599,293 @@ var init_attachments = __esm({
|
|
|
599
599
|
}
|
|
600
600
|
});
|
|
601
601
|
|
|
602
|
+
// packages/core/src/progress.ts
|
|
603
|
+
function emptyUsage() {
|
|
604
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 };
|
|
605
|
+
}
|
|
606
|
+
function addUsage(a, b) {
|
|
607
|
+
return {
|
|
608
|
+
input: a.input + b.input,
|
|
609
|
+
output: a.output + b.output,
|
|
610
|
+
cacheRead: a.cacheRead + b.cacheRead,
|
|
611
|
+
cacheWrite: a.cacheWrite + b.cacheWrite,
|
|
612
|
+
reasoning: a.reasoning + b.reasoning
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function totalTokens(u) {
|
|
616
|
+
return u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
617
|
+
}
|
|
618
|
+
function todoStatus(v) {
|
|
619
|
+
return v === "pending" || v === "in_progress" || v === "completed" ? v : null;
|
|
620
|
+
}
|
|
621
|
+
function parseTime(v) {
|
|
622
|
+
if (typeof v !== "string") return void 0;
|
|
623
|
+
const t = Date.parse(v);
|
|
624
|
+
return Number.isNaN(t) ? void 0 : t;
|
|
625
|
+
}
|
|
626
|
+
function parseLine(line) {
|
|
627
|
+
try {
|
|
628
|
+
const value = JSON.parse(line);
|
|
629
|
+
return value && typeof value === "object" ? value : null;
|
|
630
|
+
} catch {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function initialClaudeState() {
|
|
635
|
+
return { todos: null, usageById: {}, pendingTasks: {}, activity: { state: "unknown" } };
|
|
636
|
+
}
|
|
637
|
+
function claudeUsage(u) {
|
|
638
|
+
const details = u.output_tokens_details;
|
|
639
|
+
return {
|
|
640
|
+
input: num(u.input_tokens),
|
|
641
|
+
output: num(u.output_tokens),
|
|
642
|
+
cacheRead: num(u.cache_read_input_tokens),
|
|
643
|
+
cacheWrite: num(u.cache_creation_input_tokens),
|
|
644
|
+
reasoning: num(details?.thinking_tokens)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function todoWriteItems(input) {
|
|
648
|
+
if (!Array.isArray(input.todos)) return null;
|
|
649
|
+
const items = [];
|
|
650
|
+
for (const raw of input.todos) {
|
|
651
|
+
if (!raw || typeof raw !== "object") continue;
|
|
652
|
+
const t = raw;
|
|
653
|
+
const status = todoStatus(t.status);
|
|
654
|
+
if (typeof t.content !== "string" || !status) continue;
|
|
655
|
+
items.push({
|
|
656
|
+
content: t.content,
|
|
657
|
+
status,
|
|
658
|
+
...typeof t.activeForm === "string" ? { activeForm: t.activeForm } : {}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return items;
|
|
662
|
+
}
|
|
663
|
+
function applyClaudeTask(todos, name, input, result) {
|
|
664
|
+
if (name === "TaskCreate") {
|
|
665
|
+
const task = result?.task;
|
|
666
|
+
const id2 = task && (typeof task.id === "string" || typeof task.id === "number") ? String(task.id) : void 0;
|
|
667
|
+
const content = typeof input.subject === "string" ? input.subject : typeof task?.subject === "string" ? task.subject : null;
|
|
668
|
+
if (!content) return todos;
|
|
669
|
+
const item = {
|
|
670
|
+
...id2 ? { id: id2 } : {},
|
|
671
|
+
content,
|
|
672
|
+
status: "pending",
|
|
673
|
+
...typeof input.activeForm === "string" ? { activeForm: input.activeForm } : {}
|
|
674
|
+
};
|
|
675
|
+
return [...todos ?? [], item];
|
|
676
|
+
}
|
|
677
|
+
if (result && result.success === false) return todos;
|
|
678
|
+
const id = typeof input.taskId === "string" || typeof input.taskId === "number" ? String(input.taskId) : null;
|
|
679
|
+
if (!id || !todos) return todos;
|
|
680
|
+
if (input.status === "deleted") return todos.filter((t) => t.id !== id);
|
|
681
|
+
const status = todoStatus(input.status);
|
|
682
|
+
return todos.map((t) => {
|
|
683
|
+
if (t.id !== id) return t;
|
|
684
|
+
return {
|
|
685
|
+
...t,
|
|
686
|
+
...status ? { status } : {},
|
|
687
|
+
...typeof input.subject === "string" ? { content: input.subject } : {},
|
|
688
|
+
...typeof input.activeForm === "string" ? { activeForm: input.activeForm } : {}
|
|
689
|
+
};
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
function reduceClaudeLine(state, line) {
|
|
693
|
+
const entry = parseLine(line);
|
|
694
|
+
if (!entry) return state;
|
|
695
|
+
const at = parseTime(entry.timestamp);
|
|
696
|
+
const next = { ...state, startedAt: state.startedAt ?? at };
|
|
697
|
+
const message = entry.message;
|
|
698
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
699
|
+
if (entry.type === "assistant" && message) {
|
|
700
|
+
const usage = message.usage;
|
|
701
|
+
if (usage && typeof message.id === "string") {
|
|
702
|
+
const u = claudeUsage(usage);
|
|
703
|
+
next.usageById = { ...state.usageById, [message.id]: u };
|
|
704
|
+
next.contextTokens = u.input + u.cacheRead + u.cacheWrite;
|
|
705
|
+
}
|
|
706
|
+
let tool;
|
|
707
|
+
for (const block of content) {
|
|
708
|
+
if (block?.type !== "tool_use" || typeof block.name !== "string") continue;
|
|
709
|
+
tool = block.name;
|
|
710
|
+
const input = block.input && typeof block.input === "object" ? block.input : {};
|
|
711
|
+
if (block.name === "TodoWrite") {
|
|
712
|
+
const items = todoWriteItems(input);
|
|
713
|
+
if (items) next.todos = items;
|
|
714
|
+
} else if ((block.name === "TaskCreate" || block.name === "TaskUpdate") && typeof block.id === "string") {
|
|
715
|
+
next.pendingTasks = { ...next.pendingTasks, [block.id]: { name: block.name, input } };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (tool) next.activity = { state: "working", tool, at };
|
|
719
|
+
else if (message.stop_reason === "end_turn") next.activity = { state: "waiting", waitingFor: "input", at };
|
|
720
|
+
else next.activity = { state: "working", at };
|
|
721
|
+
return next;
|
|
722
|
+
}
|
|
723
|
+
if (entry.type === "user" && message) {
|
|
724
|
+
const result = entry.toolUseResult && typeof entry.toolUseResult === "object" ? entry.toolUseResult : void 0;
|
|
725
|
+
let pending = next.pendingTasks;
|
|
726
|
+
for (const block of content) {
|
|
727
|
+
if (block?.type !== "tool_result" || typeof block.tool_use_id !== "string") continue;
|
|
728
|
+
const call = pending[block.tool_use_id];
|
|
729
|
+
if (!call) continue;
|
|
730
|
+
next.todos = applyClaudeTask(next.todos, call.name, call.input, result);
|
|
731
|
+
pending = { ...pending };
|
|
732
|
+
delete pending[block.tool_use_id];
|
|
733
|
+
}
|
|
734
|
+
next.pendingTasks = pending;
|
|
735
|
+
next.activity = { state: "working", at };
|
|
736
|
+
return next;
|
|
737
|
+
}
|
|
738
|
+
return next;
|
|
739
|
+
}
|
|
740
|
+
function claudeUsageTotal(state) {
|
|
741
|
+
return Object.values(state.usageById).reduce(addUsage, emptyUsage());
|
|
742
|
+
}
|
|
743
|
+
function initialCodexState() {
|
|
744
|
+
return { todos: null, usage: emptyUsage(), activity: { state: "unknown" } };
|
|
745
|
+
}
|
|
746
|
+
function codexUsage(u) {
|
|
747
|
+
const cached2 = num(u.cached_input_tokens);
|
|
748
|
+
return {
|
|
749
|
+
input: Math.max(0, num(u.input_tokens) - cached2),
|
|
750
|
+
output: num(u.output_tokens),
|
|
751
|
+
cacheRead: cached2,
|
|
752
|
+
cacheWrite: num(u.cache_write_input_tokens),
|
|
753
|
+
reasoning: num(u.reasoning_output_tokens)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function planItems(argumentsJson) {
|
|
757
|
+
if (typeof argumentsJson !== "string") return null;
|
|
758
|
+
const args = parseLine(argumentsJson);
|
|
759
|
+
if (!args || !Array.isArray(args.plan)) return null;
|
|
760
|
+
const items = [];
|
|
761
|
+
for (const raw of args.plan) {
|
|
762
|
+
if (!raw || typeof raw !== "object") continue;
|
|
763
|
+
const p = raw;
|
|
764
|
+
const status = todoStatus(p.status);
|
|
765
|
+
if (typeof p.step === "string" && status) items.push({ content: p.step, status });
|
|
766
|
+
}
|
|
767
|
+
return items;
|
|
768
|
+
}
|
|
769
|
+
function reduceCodexLine(state, line) {
|
|
770
|
+
const entry = parseLine(line);
|
|
771
|
+
if (!entry) return state;
|
|
772
|
+
const at = parseTime(entry.timestamp);
|
|
773
|
+
const payload = entry.payload && typeof entry.payload === "object" ? entry.payload : {};
|
|
774
|
+
if (entry.type === "session_meta") {
|
|
775
|
+
return {
|
|
776
|
+
...state,
|
|
777
|
+
cwd: typeof payload.cwd === "string" ? payload.cwd : state.cwd,
|
|
778
|
+
startedAt: parseTime(payload.timestamp) ?? at ?? state.startedAt
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
if (entry.type === "response_item" && (payload.type === "function_call" || payload.type === "custom_tool_call")) {
|
|
782
|
+
const name = typeof payload.name === "string" ? payload.name : void 0;
|
|
783
|
+
const next = { ...state, activity: { state: "working", tool: name, at } };
|
|
784
|
+
if (name === "update_plan") {
|
|
785
|
+
const items = planItems(payload.arguments);
|
|
786
|
+
if (items) next.todos = items;
|
|
787
|
+
}
|
|
788
|
+
return next;
|
|
789
|
+
}
|
|
790
|
+
if (entry.type === "event_msg") {
|
|
791
|
+
if (payload.type === "token_count") {
|
|
792
|
+
const info = payload.info;
|
|
793
|
+
const total = info?.total_token_usage;
|
|
794
|
+
if (!total) return state;
|
|
795
|
+
const last = info?.last_token_usage;
|
|
796
|
+
return { ...state, usage: codexUsage(total), contextTokens: last ? num(last.input_tokens) : state.contextTokens };
|
|
797
|
+
}
|
|
798
|
+
if (payload.type === "task_complete") {
|
|
799
|
+
return { ...state, activity: { state: "waiting", waitingFor: "input", at } };
|
|
800
|
+
}
|
|
801
|
+
if (payload.type === "task_started" || payload.type === "user_message") {
|
|
802
|
+
return { ...state, activity: { state: "working", at } };
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return state;
|
|
806
|
+
}
|
|
807
|
+
function activityFromHook(event, at) {
|
|
808
|
+
if (!event || typeof event !== "object") return null;
|
|
809
|
+
const e = event;
|
|
810
|
+
switch (e.hook_event_name) {
|
|
811
|
+
case "Notification":
|
|
812
|
+
return e.notification_type === "idle_prompt" ? { state: "waiting", waitingFor: "input", at } : { state: "waiting", waitingFor: "permission", at };
|
|
813
|
+
case "Stop":
|
|
814
|
+
return { state: "waiting", waitingFor: "input", at };
|
|
815
|
+
case "UserPromptSubmit":
|
|
816
|
+
case "PostToolUse":
|
|
817
|
+
return { state: "working", at };
|
|
818
|
+
default:
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function latestActivity(a, b) {
|
|
823
|
+
if (!b) return a;
|
|
824
|
+
if (a.at == null) return b;
|
|
825
|
+
if (b.at == null) return a;
|
|
826
|
+
return b.at >= a.at ? b : a;
|
|
827
|
+
}
|
|
828
|
+
function sameItem(a, b) {
|
|
829
|
+
return a.id != null || b.id != null ? a.id === b.id : a.content === b.content;
|
|
830
|
+
}
|
|
831
|
+
function diffProgress(prev, next) {
|
|
832
|
+
if (!prev) return [];
|
|
833
|
+
const out = [];
|
|
834
|
+
const todos = next.todos ?? [];
|
|
835
|
+
const before = prev.todos ?? [];
|
|
836
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
837
|
+
for (const item of todos) {
|
|
838
|
+
if (item.status !== "completed") continue;
|
|
839
|
+
const old = before.find((b) => sameItem(b, item));
|
|
840
|
+
if (old?.status === "completed") continue;
|
|
841
|
+
out.push({ kind: "step_completed", item: item.content, done, total: todos.length });
|
|
842
|
+
}
|
|
843
|
+
const allDone = (list) => list.length > 0 && list.every((t) => t.status === "completed");
|
|
844
|
+
if (allDone(todos) && !allDone(before)) {
|
|
845
|
+
out.push({ kind: "all_completed", done, total: todos.length });
|
|
846
|
+
}
|
|
847
|
+
if (next.activity.state === "waiting" && prev.activity.state !== "waiting") {
|
|
848
|
+
out.push({ kind: "waiting_input", waitingFor: next.activity.waitingFor ?? "input" });
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
function formatTokens(n) {
|
|
853
|
+
if (n < 1e3) return String(n);
|
|
854
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
855
|
+
return `${(n / 1e6).toFixed(2)}M`;
|
|
856
|
+
}
|
|
857
|
+
function describeNotification(n) {
|
|
858
|
+
switch (n.kind) {
|
|
859
|
+
case "step_completed":
|
|
860
|
+
return `\u5B8C\u6210 ${n.done}/${n.total} \u2014 ${n.item ?? ""}`;
|
|
861
|
+
case "all_completed":
|
|
862
|
+
return "\u6240\u6709\u6B65\u9A5F\u5DF2\u5B8C\u6210";
|
|
863
|
+
case "waiting_input":
|
|
864
|
+
return n.waitingFor === "permission" ? "\u7B49\u5F85\u4F60\u5141\u8A31\u6B0A\u9650" : "\u7B49\u5F85\u4F60\u7684\u8F38\u5165";
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function progressSummary(progress, usage) {
|
|
868
|
+
const parts = [];
|
|
869
|
+
const todos = progress?.todos;
|
|
870
|
+
if (todos && todos.length) parts.push(`${todos.filter((t) => t.status === "completed").length}/${todos.length}`);
|
|
871
|
+
const total = progress?.totalUsage ?? usage;
|
|
872
|
+
if (total && totalTokens(total) > 0) parts.push(`${formatTokens(totalTokens(total))} tok`);
|
|
873
|
+
if (progress?.activity.state === "waiting") parts.push(progress.activity.waitingFor === "permission" ? "\u7B49\u5F85\u6B0A\u9650" : "\u7B49\u5F85\u8F38\u5165");
|
|
874
|
+
return parts.join(" \xB7 ");
|
|
875
|
+
}
|
|
876
|
+
var num;
|
|
877
|
+
var init_progress = __esm({
|
|
878
|
+
"packages/core/src/progress.ts"() {
|
|
879
|
+
"use strict";
|
|
880
|
+
num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
602
884
|
// packages/tui/src/task-form.ts
|
|
603
885
|
import { spawnSync } from "child_process";
|
|
604
|
-
import
|
|
605
|
-
import
|
|
606
|
-
import
|
|
886
|
+
import fs21 from "fs";
|
|
887
|
+
import os7 from "os";
|
|
888
|
+
import path25 from "path";
|
|
607
889
|
import React, { useEffect, useRef, useState } from "react";
|
|
608
890
|
import { Box, Text, render, useApp, useInput } from "ink";
|
|
609
891
|
function newFormState(defaultAutoMode, agents) {
|
|
@@ -722,17 +1004,17 @@ function editorCommand(env, platform) {
|
|
|
722
1004
|
return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
|
|
723
1005
|
}
|
|
724
1006
|
function editInEditor(text) {
|
|
725
|
-
const dir =
|
|
726
|
-
const file =
|
|
1007
|
+
const dir = fs21.mkdtempSync(path25.join(os7.tmpdir(), "vibeflow-"));
|
|
1008
|
+
const file = path25.join(dir, "description.md");
|
|
727
1009
|
try {
|
|
728
|
-
|
|
1010
|
+
fs21.writeFileSync(file, text);
|
|
729
1011
|
const cmd = editorCommand(process.env, process.platform);
|
|
730
1012
|
const r = spawnSync(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
|
|
731
1013
|
if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
|
|
732
1014
|
if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
|
|
733
|
-
return { text:
|
|
1015
|
+
return { text: fs21.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
|
|
734
1016
|
} finally {
|
|
735
|
-
|
|
1017
|
+
fs21.rmSync(dir, { recursive: true, force: true });
|
|
736
1018
|
}
|
|
737
1019
|
}
|
|
738
1020
|
function cycle(list, current, step) {
|
|
@@ -803,10 +1085,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
803
1085
|
if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
|
|
804
1086
|
else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
|
|
805
1087
|
else if (editing === "project") {
|
|
806
|
-
if (value2) setProject(
|
|
1088
|
+
if (value2) setProject(path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir())));
|
|
807
1089
|
} else if (editing === "attachments" && value2) {
|
|
808
|
-
const file =
|
|
809
|
-
if (!
|
|
1090
|
+
const file = path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir()));
|
|
1091
|
+
if (!fs21.existsSync(file) || !fs21.statSync(file).isFile()) {
|
|
810
1092
|
setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
|
|
811
1093
|
return;
|
|
812
1094
|
}
|
|
@@ -886,7 +1168,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
886
1168
|
case "branch":
|
|
887
1169
|
return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
|
|
888
1170
|
case "attachments":
|
|
889
|
-
return s.attachments.length ? s.attachments.map((a) =>
|
|
1171
|
+
return s.attachments.length ? s.attachments.map((a) => path25.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
|
|
890
1172
|
case "description": {
|
|
891
1173
|
if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
|
|
892
1174
|
const lines = s.description.split("\n");
|
|
@@ -917,7 +1199,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
917
1199
|
if (info && !info.isRepo) return "\u9019\u500B\u8CC7\u6599\u593E\u4E0D\u662F Git repository\u3002\u8ACB\u6539\u9078\u4E00\u500B Git \u5C08\u6848\uFF0C\u6216\u5728\u4E0B\u65B9\u521D\u59CB\u5316\u3002";
|
|
918
1200
|
if (s.mode === "edit" && projectChanged(s)) return "\u5206\u652F\u6703\u5728\u65B0\u5C08\u6848\u3001\u5361\u7247\u958B\u59CB\u57F7\u884C\u6642\u624D\u5EFA\u7ACB\u3002";
|
|
919
1201
|
if (info?.isRepo && !info.hasRemote) return `\u6B64 repository \u6C92\u6709 remote\uFF0C\u5C07\u4EE5\u76EE\u524D\u5206\u652F (${info.currentBranch ?? "HEAD"}) \u70BA\u57FA\u6E96\u5EFA\u7ACB\u672C\u5730 worktree\u3002`;
|
|
920
|
-
if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${
|
|
1202
|
+
if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path25.basename(s.projectPath)}`;
|
|
921
1203
|
}
|
|
922
1204
|
if (field === "branch") {
|
|
923
1205
|
const when = info?.hasRemote ? "\u958B\u59CB\u57F7\u884C\u6642\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u4E26\u63A8\u5230 origin\uFF1B\u82E5 remote \u5DF2\u6709\u9019\u500B\u5206\u652F\uFF0C\u5247\u76F4\u63A5\u53D6\u56DE\u4E26\u63A5\u7E8C\u4E0A\u9762\u7684\u5DE5\u4F5C\u3002" : "\u958B\u59CB\u57F7\u884C\u6642\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u3002";
|
|
@@ -1037,7 +1319,7 @@ __export(src_exports, {
|
|
|
1037
1319
|
showDiff: () => showDiff
|
|
1038
1320
|
});
|
|
1039
1321
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1040
|
-
import
|
|
1322
|
+
import path26 from "path";
|
|
1041
1323
|
import React2, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1042
1324
|
import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
|
|
1043
1325
|
function findDetachKey(text) {
|
|
@@ -1092,10 +1374,18 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1092
1374
|
const [message, setMessage] = useState2(initialMessage ?? "");
|
|
1093
1375
|
const [confirm, setConfirm] = useState2(null);
|
|
1094
1376
|
const [help, setHelp] = useState2(false);
|
|
1377
|
+
const [progress, setProgress] = useState2({});
|
|
1095
1378
|
useEffect2(() => {
|
|
1096
1379
|
let alive = true;
|
|
1097
1380
|
void api.getState().then((s) => alive && setBoard2(s.board));
|
|
1098
1381
|
const off = api.onStateChanged((s) => setBoard2(s.board));
|
|
1382
|
+
const offProgress = api.onProgressUpdate(({ taskId, progress: p }) => {
|
|
1383
|
+
setProgress((prev) => ({ ...prev, [taskId]: p }));
|
|
1384
|
+
});
|
|
1385
|
+
const offNotify = api.onProgressNotify(({ title, notifications }) => {
|
|
1386
|
+
const last = notifications[notifications.length - 1];
|
|
1387
|
+
if (last) setMessage(`\u300C${title}\u300D${describeNotification(last)}`);
|
|
1388
|
+
});
|
|
1099
1389
|
const poll = async () => {
|
|
1100
1390
|
try {
|
|
1101
1391
|
const info = await api.term.list();
|
|
@@ -1110,12 +1400,27 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1110
1400
|
return () => {
|
|
1111
1401
|
alive = false;
|
|
1112
1402
|
off();
|
|
1403
|
+
offProgress();
|
|
1404
|
+
offNotify();
|
|
1113
1405
|
clearInterval(timer);
|
|
1114
1406
|
};
|
|
1115
1407
|
}, [api]);
|
|
1116
1408
|
useEffect2(() => {
|
|
1117
1409
|
if (board) setSel((s) => clampSelection(board, s));
|
|
1118
1410
|
}, [board]);
|
|
1411
|
+
const runningIds = board ? board.in_progress.map((t) => t.id).join(",") : "";
|
|
1412
|
+
useEffect2(() => {
|
|
1413
|
+
let alive = true;
|
|
1414
|
+
for (const id of runningIds ? runningIds.split(",") : []) {
|
|
1415
|
+
void api.getProgress(id).then((p) => {
|
|
1416
|
+
if (alive && p) setProgress((prev) => prev[id] ? prev : { ...prev, [id]: p });
|
|
1417
|
+
}).catch(() => {
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
return () => {
|
|
1421
|
+
alive = false;
|
|
1422
|
+
};
|
|
1423
|
+
}, [api, runningIds]);
|
|
1119
1424
|
const selected = board ? board[COLUMNS2[sel.column]][sel.index] : void 0;
|
|
1120
1425
|
const leave = (action) => {
|
|
1121
1426
|
onAction(action, sel);
|
|
@@ -1259,10 +1564,12 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1259
1564
|
...tasks.length === 0 ? [h2(Text2, { key: "empty", dimColor: true }, "\u2014")] : tasks.map((t, ti) => {
|
|
1260
1565
|
const isSel = active && ti === sel.index;
|
|
1261
1566
|
const mark = running.has(t.id) ? "\u25CF " : " ";
|
|
1567
|
+
const summary = col === "backlog" ? "" : progressSummary(col === "in_progress" ? progress[t.id] : null, t.usage);
|
|
1262
1568
|
return h2(
|
|
1263
1569
|
Box2,
|
|
1264
1570
|
{ key: t.id, flexDirection: "column" },
|
|
1265
1571
|
h2(Text2, { inverse: isSel, color: running.has(t.id) ? "green" : void 0 }, truncate2(`${mark}${t.title}`, colWidth - 4)),
|
|
1572
|
+
summary ? h2(Text2, { dimColor: !isSel, color: isSel ? "cyan" : void 0 }, truncate2(` ${summary}`, colWidth - 4)) : null,
|
|
1266
1573
|
isSel ? h2(Text2, { dimColor: true }, truncate2(` ${t.projectName ?? ""} \xB7 ${t.branch}`, colWidth - 4)) : null
|
|
1267
1574
|
);
|
|
1268
1575
|
})
|
|
@@ -1308,7 +1615,7 @@ function showDiff(task) {
|
|
|
1308
1615
|
});
|
|
1309
1616
|
}
|
|
1310
1617
|
function tmuxAttach(opts, sessionName) {
|
|
1311
|
-
const conf =
|
|
1618
|
+
const conf = path26.join(opts.userDataDir, "tmux.conf");
|
|
1312
1619
|
const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
|
|
1313
1620
|
const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
|
|
1314
1621
|
spawnSync2("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
|
|
@@ -1422,6 +1729,7 @@ var h2, COLUMNS2, COLUMN_TITLES, DETACH_KEY, RESET_TERMINAL_MODES, CLEAR_SCREEN;
|
|
|
1422
1729
|
var init_src = __esm({
|
|
1423
1730
|
"packages/tui/src/index.ts"() {
|
|
1424
1731
|
"use strict";
|
|
1732
|
+
init_progress();
|
|
1425
1733
|
init_tmux_backend();
|
|
1426
1734
|
init_task_form();
|
|
1427
1735
|
h2 = React2.createElement;
|
|
@@ -1438,8 +1746,8 @@ var init_src = __esm({
|
|
|
1438
1746
|
});
|
|
1439
1747
|
|
|
1440
1748
|
// packages/cli/src/main.ts
|
|
1441
|
-
import
|
|
1442
|
-
import
|
|
1749
|
+
import fs22 from "fs";
|
|
1750
|
+
import path27 from "path";
|
|
1443
1751
|
import { fileURLToPath } from "url";
|
|
1444
1752
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
1445
1753
|
|
|
@@ -1775,6 +2083,21 @@ function recordRecentProject(store, projectPath) {
|
|
|
1775
2083
|
}
|
|
1776
2084
|
|
|
1777
2085
|
// packages/core/src/store.ts
|
|
2086
|
+
var DEFAULT_NOTIFICATION_SETTINGS = {
|
|
2087
|
+
enabled: true,
|
|
2088
|
+
stepCompleted: false,
|
|
2089
|
+
allCompleted: true,
|
|
2090
|
+
waitingInput: true,
|
|
2091
|
+
desktop: false
|
|
2092
|
+
};
|
|
2093
|
+
function resolveNotificationSettings(settings) {
|
|
2094
|
+
const stored = settings?.notifications ?? {};
|
|
2095
|
+
const out = { ...DEFAULT_NOTIFICATION_SETTINGS };
|
|
2096
|
+
for (const key of Object.keys(out)) {
|
|
2097
|
+
if (typeof stored[key] === "boolean") out[key] = stored[key];
|
|
2098
|
+
}
|
|
2099
|
+
return out;
|
|
2100
|
+
}
|
|
1778
2101
|
function resolveWorkstationPath(settings) {
|
|
1779
2102
|
const p = settings?.workstationPath?.trim();
|
|
1780
2103
|
return p && p.length > 0 ? p : join(homedir2(), "Desktop");
|
|
@@ -1832,6 +2155,11 @@ function getSettings() {
|
|
|
1832
2155
|
}
|
|
1833
2156
|
function setSettings(patch) {
|
|
1834
2157
|
const next = { ...getSettings(), ...patch };
|
|
2158
|
+
if (patch.notifications !== void 0) {
|
|
2159
|
+
next.notifications = resolveNotificationSettings({
|
|
2160
|
+
notifications: { ...resolveNotificationSettings(getSettings()), ...patch.notifications ?? {} }
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
1835
2163
|
if (typeof next.systemPrompt === "string" && next.systemPrompt.trim() === "") {
|
|
1836
2164
|
delete next.systemPrompt;
|
|
1837
2165
|
}
|
|
@@ -1962,6 +2290,16 @@ function createBridge(t) {
|
|
|
1962
2290
|
return t.on("subagents:update", (payload) => callback(payload));
|
|
1963
2291
|
},
|
|
1964
2292
|
deleteTask: (taskId) => t.invoke("vibeflow:deleteTask", taskId),
|
|
2293
|
+
/** A running card's progress now (todos, usage, activity); null = nothing to show yet. */
|
|
2294
|
+
getProgress: (taskId) => t.invoke("progress:get", taskId),
|
|
2295
|
+
/** Every change to a card's progress, read from its agent's transcript. */
|
|
2296
|
+
onProgressUpdate: (callback) => {
|
|
2297
|
+
return t.on("progress:update", (payload) => callback(payload));
|
|
2298
|
+
},
|
|
2299
|
+
/** Stage notifications, already filtered by the user's notification settings. */
|
|
2300
|
+
onProgressNotify: (callback) => {
|
|
2301
|
+
return t.on("progress:notify", (payload) => callback(payload));
|
|
2302
|
+
},
|
|
1965
2303
|
attachments: {
|
|
1966
2304
|
write: (payload) => t.invoke("attachments:write", payload)
|
|
1967
2305
|
},
|
|
@@ -2357,103 +2695,287 @@ function unwatchAllSubAgents() {
|
|
|
2357
2695
|
}
|
|
2358
2696
|
}
|
|
2359
2697
|
|
|
2360
|
-
// packages/core/src/
|
|
2361
|
-
init_env();
|
|
2362
|
-
init_attachments();
|
|
2363
|
-
|
|
2364
|
-
// packages/core/src/artifacts.ts
|
|
2365
|
-
import fs7 from "fs";
|
|
2366
|
-
import path11 from "path";
|
|
2698
|
+
// packages/core/src/launch.ts
|
|
2367
2699
|
var ARTIFACTS_DIR_SUFFIX = ".artifacts";
|
|
2368
2700
|
var SCRATCH_DIR_NAME = "scratch";
|
|
2369
2701
|
var ARTIFACTS_FALLBACK_DIR = ".vibeflow-artifacts";
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
".gif": "image/gif",
|
|
2375
|
-
".webp": "image/webp",
|
|
2376
|
-
".avif": "image/avif",
|
|
2377
|
-
".bmp": "image/bmp",
|
|
2378
|
-
".svg": "image/svg+xml"
|
|
2379
|
-
};
|
|
2380
|
-
var VIDEO_MIME = {
|
|
2381
|
-
".mp4": "video/mp4",
|
|
2382
|
-
".m4v": "video/mp4",
|
|
2383
|
-
".webm": "video/webm",
|
|
2384
|
-
".mov": "video/mp4"
|
|
2385
|
-
};
|
|
2386
|
-
var MAX_VIDEO_BYTES = 20 * 1024 * 1024;
|
|
2387
|
-
var MAX_TEXT_BYTES = 256 * 1024;
|
|
2388
|
-
var MAX_DEPTH = 3;
|
|
2389
|
-
var MAX_ARTIFACTS = 200;
|
|
2390
|
-
var SNIFF_BYTES = 8 * 1024;
|
|
2391
|
-
function agentArtifactsPath(baseDir, worktreePath) {
|
|
2392
|
-
return path11.join(baseDir, `${path11.basename(worktreePath)}${ARTIFACTS_DIR_SUFFIX}`);
|
|
2702
|
+
function pathBasename(p) {
|
|
2703
|
+
const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
2704
|
+
const i = norm.lastIndexOf("/");
|
|
2705
|
+
return i >= 0 ? norm.slice(i + 1) : norm;
|
|
2393
2706
|
}
|
|
2394
|
-
function
|
|
2395
|
-
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2707
|
+
function agentArtifactsDir(worktreePath, workspacePath) {
|
|
2708
|
+
if (!worktreePath || !workspacePath) return null;
|
|
2709
|
+
const dir = toShellPath(workspacePath);
|
|
2710
|
+
const ws = pathBasename(worktreePath);
|
|
2711
|
+
return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX}`;
|
|
2712
|
+
}
|
|
2713
|
+
function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR) {
|
|
2714
|
+
return [
|
|
2715
|
+
"Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
|
|
2716
|
+
`1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
|
|
2717
|
+
`2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
|
|
2718
|
+
`3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME}/\u3002`,
|
|
2719
|
+
`4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
|
|
2720
|
+
"5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
|
|
2721
|
+
].join("\n");
|
|
2722
|
+
}
|
|
2723
|
+
var AUTO_PERMISSION_MODE = "auto";
|
|
2724
|
+
function claudePermissionFlag(autoMode) {
|
|
2725
|
+
return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
|
|
2726
|
+
}
|
|
2727
|
+
function shellQuote(s) {
|
|
2728
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
2729
|
+
}
|
|
2730
|
+
function toShellPath(p) {
|
|
2731
|
+
return p.replace(/\\/g, "/");
|
|
2732
|
+
}
|
|
2733
|
+
function boardEnvPrefix(task, opts) {
|
|
2734
|
+
const board = opts?.boardCli;
|
|
2735
|
+
if (!board) return "";
|
|
2736
|
+
const entries = [
|
|
2737
|
+
["VIBEFLOW_TASK_ID", task.id],
|
|
2738
|
+
["VIBEFLOW_PROJECT_PATH", task.projectPath],
|
|
2739
|
+
["VIBEFLOW_BRANCH", task.branch],
|
|
2740
|
+
["VIBEFLOW_BASE_BRANCH", task.baseBranch],
|
|
2741
|
+
["VIBEFLOW_STORE_DIR", board?.storeDir],
|
|
2742
|
+
["VIBEFLOW_CLI", board?.cliPath],
|
|
2743
|
+
["VIBEFLOW_CLI_LOADER", board?.loaderPath],
|
|
2744
|
+
["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
|
|
2745
|
+
];
|
|
2746
|
+
const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
|
|
2747
|
+
return exports.length ? `export ${exports.join(" ")}; ` : "";
|
|
2748
|
+
}
|
|
2749
|
+
function codexAutoFlag(autoMode) {
|
|
2750
|
+
return autoMode ? "--dangerously-bypass-approvals-and-sandbox " : "";
|
|
2751
|
+
}
|
|
2752
|
+
function claudeResumeOrFresh(sessionId, worktreePath, tail) {
|
|
2753
|
+
if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
|
|
2754
|
+
const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2755
|
+
const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
|
|
2756
|
+
return `if [ -f ${sessionFile} ]; then claude --resume ${sessionId} ${tail}; else claude --session-id ${sessionId} ${tail}; fi\r`;
|
|
2757
|
+
}
|
|
2758
|
+
var SUBAGENTS_DIR2 = ".vibeflow-subagents";
|
|
2759
|
+
var PROGRESS_EVENTS_DIR = ".vibeflow-events";
|
|
2760
|
+
var TODO_TOOLS = "TodoWrite|TaskCreate|TaskUpdate";
|
|
2761
|
+
function buildClaudeSettings(worktreePath) {
|
|
2762
|
+
const settings = { theme: "dark" };
|
|
2763
|
+
if (worktreePath) {
|
|
2764
|
+
const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
|
|
2765
|
+
const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
|
|
2766
|
+
const taskHook = {
|
|
2767
|
+
matcher: "Task",
|
|
2768
|
+
hooks: [{ type: "command", command }]
|
|
2769
|
+
};
|
|
2770
|
+
const eventsDir = `${toShellPath(worktreePath)}/${PROGRESS_EVENTS_DIR}`;
|
|
2771
|
+
const progress = {
|
|
2772
|
+
hooks: [{ type: "command", command: `mkdir -p "${eventsDir}" && cat > "${eventsDir}/$(date +%s)-$$-$RANDOM.json"; exit 0` }]
|
|
2773
|
+
};
|
|
2774
|
+
settings.hooks = {
|
|
2775
|
+
PreToolUse: [taskHook],
|
|
2776
|
+
PostToolUse: [taskHook, { matcher: TODO_TOOLS, ...progress }],
|
|
2777
|
+
Stop: [progress],
|
|
2778
|
+
Notification: [progress],
|
|
2779
|
+
UserPromptSubmit: [progress]
|
|
2780
|
+
};
|
|
2401
2781
|
}
|
|
2402
|
-
return
|
|
2782
|
+
return JSON.stringify(settings);
|
|
2403
2783
|
}
|
|
2404
|
-
function
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
return "binary";
|
|
2413
|
-
} finally {
|
|
2414
|
-
if (fd !== void 0) {
|
|
2415
|
-
try {
|
|
2416
|
-
fs7.closeSync(fd);
|
|
2417
|
-
} catch {
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2784
|
+
function resolveSystemPrompt(custom) {
|
|
2785
|
+
return custom && custom.trim() ? custom : "";
|
|
2786
|
+
}
|
|
2787
|
+
function buildPrompt(task) {
|
|
2788
|
+
const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
|
|
2789
|
+
const description = task.description?.trim();
|
|
2790
|
+
if (description) {
|
|
2791
|
+
lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
|
|
2420
2792
|
}
|
|
2793
|
+
return lines.join("\n");
|
|
2421
2794
|
}
|
|
2422
|
-
function
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2795
|
+
function executorSessionId(taskId, runId) {
|
|
2796
|
+
const source = runId ? `${taskId}:${runId}` : taskId;
|
|
2797
|
+
const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
|
|
2798
|
+
const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
|
|
2799
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
2800
|
+
}
|
|
2801
|
+
function namespaceHash(namespace) {
|
|
2802
|
+
let hash = 2166136261;
|
|
2803
|
+
for (let i = 0; i < namespace.length; i += 1) {
|
|
2804
|
+
hash ^= namespace.charCodeAt(i);
|
|
2805
|
+
hash = Math.imul(hash, 16777619);
|
|
2430
2806
|
}
|
|
2431
|
-
|
|
2432
|
-
return target;
|
|
2807
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
2433
2808
|
}
|
|
2434
|
-
function
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2809
|
+
function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
|
|
2810
|
+
let cmd;
|
|
2811
|
+
if (agent === "claude") {
|
|
2812
|
+
const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
|
|
2813
|
+
const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
|
|
2814
|
+
const modelFlag = model ? ` --model ${model}` : "";
|
|
2815
|
+
const effortFlag = effort ? ` --effort ${effort}` : "";
|
|
2816
|
+
const library = opts?.library;
|
|
2817
|
+
const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
|
|
2818
|
+
const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
|
|
2819
|
+
const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
|
|
2820
|
+
const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
|
|
2821
|
+
const tail = `${flags}${sysFlag}${promptArg}`;
|
|
2822
|
+
cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
|
|
2823
|
+
} else {
|
|
2824
|
+
const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
|
|
2825
|
+
const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
|
|
2826
|
+
const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
|
|
2827
|
+
const promptArg = combined ? ` ${shellQuote(combined)}` : "";
|
|
2828
|
+
cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
|
|
2829
|
+
}
|
|
2830
|
+
if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
|
|
2831
|
+
return cmd;
|
|
2832
|
+
}
|
|
2833
|
+
var AGENT_NAMES = {
|
|
2834
|
+
claude: "Claude Code",
|
|
2835
|
+
codex: "Codex CLI"
|
|
2836
|
+
};
|
|
2837
|
+
var DEFAULT_MODELS = {
|
|
2838
|
+
claude: "sonnet",
|
|
2839
|
+
codex: "gpt-5.5"
|
|
2840
|
+
};
|
|
2841
|
+
var LEGACY_MODEL_FALLBACKS = {
|
|
2842
|
+
codex: {
|
|
2843
|
+
"gpt-5-codex": "gpt-5.5",
|
|
2844
|
+
"gpt-5": "gpt-5.5"
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
function normalizeModel(agent, model) {
|
|
2848
|
+
return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
|
|
2849
|
+
}
|
|
2850
|
+
function taskAgent(task) {
|
|
2851
|
+
const agent = task.agentCli;
|
|
2852
|
+
return agent && agent in AGENT_NAMES ? agent : "claude";
|
|
2853
|
+
}
|
|
2854
|
+
function taskModel(task) {
|
|
2855
|
+
const agent = taskAgent(task);
|
|
2856
|
+
return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
|
|
2857
|
+
}
|
|
2858
|
+
function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
|
|
2859
|
+
const agent = taskAgent(task);
|
|
2860
|
+
const model = taskModel(task);
|
|
2861
|
+
const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR;
|
|
2862
|
+
const builtInPrompt = buildArtifactPrompt(artifactsDir);
|
|
2863
|
+
const libraryPrompt = opts?.library?.promptText?.trim();
|
|
2864
|
+
const customPrompt = resolveSystemPrompt(systemPrompt);
|
|
2865
|
+
const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
|
|
2866
|
+
const includeTaskPrompt = opts?.includeTaskPrompt !== false;
|
|
2867
|
+
const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
|
|
2868
|
+
const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
|
|
2869
|
+
return boardEnvPrefix(task, opts) + assembleCommand(
|
|
2870
|
+
agent,
|
|
2871
|
+
sys,
|
|
2872
|
+
prompt,
|
|
2873
|
+
model,
|
|
2874
|
+
task.effort,
|
|
2875
|
+
opts,
|
|
2876
|
+
task.worktreePath,
|
|
2877
|
+
sessionId,
|
|
2878
|
+
workspacePath
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// packages/core/src/git.ts
|
|
2883
|
+
init_env();
|
|
2884
|
+
init_attachments();
|
|
2885
|
+
|
|
2886
|
+
// packages/core/src/artifacts.ts
|
|
2887
|
+
import fs7 from "fs";
|
|
2888
|
+
import path11 from "path";
|
|
2889
|
+
var ARTIFACTS_DIR_SUFFIX2 = ".artifacts";
|
|
2890
|
+
var SCRATCH_DIR_NAME2 = "scratch";
|
|
2891
|
+
var ARTIFACTS_FALLBACK_DIR2 = ".vibeflow-artifacts";
|
|
2892
|
+
var IMAGE_MIME = {
|
|
2893
|
+
".png": "image/png",
|
|
2894
|
+
".jpg": "image/jpeg",
|
|
2895
|
+
".jpeg": "image/jpeg",
|
|
2896
|
+
".gif": "image/gif",
|
|
2897
|
+
".webp": "image/webp",
|
|
2898
|
+
".avif": "image/avif",
|
|
2899
|
+
".bmp": "image/bmp",
|
|
2900
|
+
".svg": "image/svg+xml"
|
|
2901
|
+
};
|
|
2902
|
+
var VIDEO_MIME = {
|
|
2903
|
+
".mp4": "video/mp4",
|
|
2904
|
+
".m4v": "video/mp4",
|
|
2905
|
+
".webm": "video/webm",
|
|
2906
|
+
".mov": "video/mp4"
|
|
2907
|
+
};
|
|
2908
|
+
var MAX_VIDEO_BYTES = 20 * 1024 * 1024;
|
|
2909
|
+
var MAX_TEXT_BYTES = 256 * 1024;
|
|
2910
|
+
var MAX_DEPTH = 3;
|
|
2911
|
+
var MAX_ARTIFACTS = 200;
|
|
2912
|
+
var SNIFF_BYTES = 8 * 1024;
|
|
2913
|
+
function agentArtifactsPath(baseDir, worktreePath) {
|
|
2914
|
+
return path11.join(baseDir, `${path11.basename(worktreePath)}${ARTIFACTS_DIR_SUFFIX2}`);
|
|
2915
|
+
}
|
|
2916
|
+
function mediaKind(filePath, size) {
|
|
2917
|
+
const extension = path11.extname(filePath).toLowerCase();
|
|
2918
|
+
const imageMime = IMAGE_MIME[extension];
|
|
2919
|
+
if (imageMime) return { kind: "image", mime: imageMime };
|
|
2920
|
+
const videoMime = VIDEO_MIME[extension];
|
|
2921
|
+
if (videoMime) {
|
|
2922
|
+
return size > MAX_VIDEO_BYTES ? { kind: "binary" } : { kind: "video", mime: videoMime };
|
|
2923
|
+
}
|
|
2924
|
+
return null;
|
|
2925
|
+
}
|
|
2926
|
+
function sniffKind(filePath) {
|
|
2927
|
+
let fd;
|
|
2928
|
+
try {
|
|
2929
|
+
fd = fs7.openSync(filePath, "r");
|
|
2930
|
+
const buf = Buffer.alloc(SNIFF_BYTES);
|
|
2931
|
+
const read = fs7.readSync(fd, buf, 0, SNIFF_BYTES, 0);
|
|
2932
|
+
return buf.subarray(0, read).includes(0) ? "binary" : "text";
|
|
2933
|
+
} catch {
|
|
2934
|
+
return "binary";
|
|
2935
|
+
} finally {
|
|
2936
|
+
if (fd !== void 0) {
|
|
2937
|
+
try {
|
|
2938
|
+
fs7.closeSync(fd);
|
|
2939
|
+
} catch {
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
function resolveInside(dir, name) {
|
|
2945
|
+
let root;
|
|
2946
|
+
let target;
|
|
2947
|
+
try {
|
|
2948
|
+
root = fs7.realpathSync(path11.resolve(dir));
|
|
2949
|
+
target = fs7.realpathSync(path11.resolve(root, name));
|
|
2950
|
+
} catch {
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
if (target !== root && !target.startsWith(root + path11.sep)) return null;
|
|
2954
|
+
return target;
|
|
2955
|
+
}
|
|
2956
|
+
function listArtifacts(dir) {
|
|
2957
|
+
const found2 = [];
|
|
2958
|
+
const walk = (current, prefix, depth, scratch) => {
|
|
2959
|
+
if (found2.length >= MAX_ARTIFACTS) return;
|
|
2960
|
+
let dirents;
|
|
2961
|
+
try {
|
|
2962
|
+
dirents = fs7.readdirSync(current, { withFileTypes: true });
|
|
2963
|
+
} catch {
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
for (const dirent of dirents) {
|
|
2967
|
+
if (found2.length >= MAX_ARTIFACTS) return;
|
|
2968
|
+
const name = prefix ? `${prefix}/${dirent.name}` : dirent.name;
|
|
2969
|
+
const full = path11.join(current, dirent.name);
|
|
2970
|
+
if (dirent.isDirectory()) {
|
|
2971
|
+
if (!scratch && depth === 1 && dirent.name === SCRATCH_DIR_NAME2) continue;
|
|
2972
|
+
if (depth < MAX_DEPTH) walk(full, name, depth + 1, scratch);
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2975
|
+
if (!dirent.isFile()) continue;
|
|
2976
|
+
let stat;
|
|
2977
|
+
try {
|
|
2978
|
+
stat = fs7.statSync(full);
|
|
2457
2979
|
} catch {
|
|
2458
2980
|
continue;
|
|
2459
2981
|
}
|
|
@@ -2470,7 +2992,7 @@ function listArtifacts(dir) {
|
|
|
2470
2992
|
}
|
|
2471
2993
|
};
|
|
2472
2994
|
walk(dir, "", 1, false);
|
|
2473
|
-
walk(path11.join(dir,
|
|
2995
|
+
walk(path11.join(dir, SCRATCH_DIR_NAME2), SCRATCH_DIR_NAME2, 2, true);
|
|
2474
2996
|
return found2.sort(
|
|
2475
2997
|
(a, b) => Number(a.scratch) - Number(b.scratch) || b.modifiedAt - a.modifiedAt
|
|
2476
2998
|
);
|
|
@@ -2637,7 +3159,13 @@ async function ensureLocalExclude(projectPath) {
|
|
|
2637
3159
|
);
|
|
2638
3160
|
await appendLineIfMissing(
|
|
2639
3161
|
path12.join(infoDir, "exclude"),
|
|
2640
|
-
`${
|
|
3162
|
+
`${PROGRESS_EVENTS_DIR}/`,
|
|
3163
|
+
"# VibeFlow progress hook events (runtime-only)",
|
|
3164
|
+
{ mkdir: true }
|
|
3165
|
+
);
|
|
3166
|
+
await appendLineIfMissing(
|
|
3167
|
+
path12.join(infoDir, "exclude"),
|
|
3168
|
+
`${ARTIFACTS_FALLBACK_DIR2}/`,
|
|
2641
3169
|
"# VibeFlow task artifacts (runtime-only)",
|
|
2642
3170
|
{ mkdir: true }
|
|
2643
3171
|
);
|
|
@@ -2727,7 +3255,7 @@ async function resolveBranchName(projectPath, workspacePath, taskId, preferredBr
|
|
|
2727
3255
|
}
|
|
2728
3256
|
return preferredBranch;
|
|
2729
3257
|
}
|
|
2730
|
-
var RUNTIME_ARTIFACT_DENYLIST = /* @__PURE__ */ new Set([SUBAGENTS_DIR]);
|
|
3258
|
+
var RUNTIME_ARTIFACT_DENYLIST = /* @__PURE__ */ new Set([SUBAGENTS_DIR, PROGRESS_EVENTS_DIR]);
|
|
2731
3259
|
var MAX_IGNORED_COPY_BYTES = 10 * 1024 * 1024;
|
|
2732
3260
|
var MAX_IGNORED_COPY_FILES = 200;
|
|
2733
3261
|
var HEAVY_IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -2998,7 +3526,7 @@ async function resetWorktreeToBase(worktreePath, baseBranch) {
|
|
|
2998
3526
|
const baseRef = await resolveBaseRef(worktreePath, baseBranch);
|
|
2999
3527
|
await git(worktreePath, ["reset", "--hard", baseRef]);
|
|
3000
3528
|
await git(worktreePath, ["clean", "-fd"]);
|
|
3001
|
-
await fs8.rm(path12.join(worktreePath,
|
|
3529
|
+
await fs8.rm(path12.join(worktreePath, ARTIFACTS_FALLBACK_DIR2), {
|
|
3002
3530
|
recursive: true,
|
|
3003
3531
|
force: true
|
|
3004
3532
|
});
|
|
@@ -3082,7 +3610,9 @@ async function collectDiffEntries(worktreePath, baseRef) {
|
|
|
3082
3610
|
}
|
|
3083
3611
|
}
|
|
3084
3612
|
const entries = Array.from(entryMap.values()).filter(
|
|
3085
|
-
(e) =>
|
|
3613
|
+
(e) => ![SUBAGENTS_DIR, PROGRESS_EVENTS_DIR].some(
|
|
3614
|
+
(dir) => e.path === dir || e.path.startsWith(`${dir}/`)
|
|
3615
|
+
)
|
|
3086
3616
|
);
|
|
3087
3617
|
await Promise.all(
|
|
3088
3618
|
entries.map(async (entry) => {
|
|
@@ -3714,8 +4244,8 @@ async function taskCommand(argv) {
|
|
|
3714
4244
|
}
|
|
3715
4245
|
|
|
3716
4246
|
// packages/cli/src/host.ts
|
|
3717
|
-
import
|
|
3718
|
-
import
|
|
4247
|
+
import fs19 from "fs";
|
|
4248
|
+
import path23 from "path";
|
|
3719
4249
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3720
4250
|
|
|
3721
4251
|
// packages/core/src/events.ts
|
|
@@ -4253,9 +4783,9 @@ async function syncBuiltinsAtStartup() {
|
|
|
4253
4783
|
}
|
|
4254
4784
|
|
|
4255
4785
|
// packages/core/src/service.ts
|
|
4256
|
-
import
|
|
4257
|
-
import
|
|
4258
|
-
import
|
|
4786
|
+
import fs17 from "fs";
|
|
4787
|
+
import os6 from "os";
|
|
4788
|
+
import path21 from "path";
|
|
4259
4789
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4260
4790
|
|
|
4261
4791
|
// packages/core/src/agent-models.ts
|
|
@@ -4889,176 +5419,288 @@ function startChatSend(opts, sender) {
|
|
|
4889
5419
|
// packages/core/src/service.ts
|
|
4890
5420
|
init_attachments();
|
|
4891
5421
|
|
|
4892
|
-
// packages/core/src/
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
return
|
|
4900
|
-
}
|
|
4901
|
-
function
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
|
|
4908
|
-
return [
|
|
4909
|
-
"Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
|
|
4910
|
-
`1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
|
|
4911
|
-
`2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
|
|
4912
|
-
`3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME2}/\u3002`,
|
|
4913
|
-
`4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
|
|
4914
|
-
"5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
|
|
4915
|
-
].join("\n");
|
|
4916
|
-
}
|
|
4917
|
-
var AUTO_PERMISSION_MODE = "auto";
|
|
4918
|
-
function claudePermissionFlag(autoMode) {
|
|
4919
|
-
return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
|
|
4920
|
-
}
|
|
4921
|
-
function shellQuote(s) {
|
|
4922
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4923
|
-
}
|
|
4924
|
-
function toShellPath(p) {
|
|
4925
|
-
return p.replace(/\\/g, "/");
|
|
4926
|
-
}
|
|
4927
|
-
function boardEnvPrefix(task, opts) {
|
|
4928
|
-
const board = opts?.boardCli;
|
|
4929
|
-
if (!board) return "";
|
|
4930
|
-
const entries = [
|
|
4931
|
-
["VIBEFLOW_TASK_ID", task.id],
|
|
4932
|
-
["VIBEFLOW_PROJECT_PATH", task.projectPath],
|
|
4933
|
-
["VIBEFLOW_BRANCH", task.branch],
|
|
4934
|
-
["VIBEFLOW_BASE_BRANCH", task.baseBranch],
|
|
4935
|
-
["VIBEFLOW_STORE_DIR", board?.storeDir],
|
|
4936
|
-
["VIBEFLOW_CLI", board?.cliPath],
|
|
4937
|
-
["VIBEFLOW_CLI_LOADER", board?.loaderPath],
|
|
4938
|
-
["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
|
|
4939
|
-
];
|
|
4940
|
-
const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
|
|
4941
|
-
return exports.length ? `export ${exports.join(" ")}; ` : "";
|
|
5422
|
+
// packages/core/src/progress-tracker.ts
|
|
5423
|
+
init_progress();
|
|
5424
|
+
import fs16 from "fs";
|
|
5425
|
+
import os5 from "os";
|
|
5426
|
+
import path20 from "path";
|
|
5427
|
+
var SINCE_SLACK_MS = 5e3;
|
|
5428
|
+
function claudeProjectDir(home, cwd) {
|
|
5429
|
+
return path20.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
|
|
5430
|
+
}
|
|
5431
|
+
function samePath(a, b) {
|
|
5432
|
+
const norm = (p) => {
|
|
5433
|
+
const r = path20.resolve(p).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
5434
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
5435
|
+
};
|
|
5436
|
+
return norm(a) === norm(b);
|
|
4942
5437
|
}
|
|
4943
|
-
function
|
|
4944
|
-
|
|
5438
|
+
function listFiles(dir, test) {
|
|
5439
|
+
try {
|
|
5440
|
+
return fs16.readdirSync(dir).filter(test).map((name) => path20.join(dir, name));
|
|
5441
|
+
} catch {
|
|
5442
|
+
return [];
|
|
5443
|
+
}
|
|
4945
5444
|
}
|
|
4946
|
-
function
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
5445
|
+
function firstLine(file) {
|
|
5446
|
+
let fd;
|
|
5447
|
+
try {
|
|
5448
|
+
fd = fs16.openSync(file, "r");
|
|
5449
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
5450
|
+
const n = fs16.readSync(fd, buf, 0, buf.length, 0);
|
|
5451
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
5452
|
+
const nl = text.indexOf("\n");
|
|
5453
|
+
return nl === -1 ? null : text.slice(0, nl);
|
|
5454
|
+
} catch {
|
|
5455
|
+
return null;
|
|
5456
|
+
} finally {
|
|
5457
|
+
if (fd !== void 0) fs16.closeSync(fd);
|
|
5458
|
+
}
|
|
4951
5459
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
5460
|
+
function advance(file, cursor, reduce, initial) {
|
|
5461
|
+
let size;
|
|
5462
|
+
try {
|
|
5463
|
+
size = fs16.statSync(file).size;
|
|
5464
|
+
} catch {
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
if (size < cursor.offset) {
|
|
5468
|
+
cursor.offset = 0;
|
|
5469
|
+
cursor.rest = Buffer.alloc(0);
|
|
5470
|
+
cursor.state = initial();
|
|
5471
|
+
}
|
|
5472
|
+
if (size === cursor.offset) return;
|
|
5473
|
+
let fd;
|
|
5474
|
+
try {
|
|
5475
|
+
fd = fs16.openSync(file, "r");
|
|
5476
|
+
const chunk = Buffer.alloc(size - cursor.offset);
|
|
5477
|
+
const n = fs16.readSync(fd, chunk, 0, chunk.length, cursor.offset);
|
|
5478
|
+
cursor.offset += n;
|
|
5479
|
+
const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
|
|
5480
|
+
const nl = data.lastIndexOf(10);
|
|
5481
|
+
if (nl === -1) {
|
|
5482
|
+
cursor.rest = data;
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
cursor.rest = data.subarray(nl + 1);
|
|
5486
|
+
for (const line of data.subarray(0, nl).toString("utf8").split("\n")) {
|
|
5487
|
+
if (line.trim()) cursor.state = reduce(cursor.state, line);
|
|
5488
|
+
}
|
|
5489
|
+
} catch {
|
|
5490
|
+
} finally {
|
|
5491
|
+
if (fd !== void 0) fs16.closeSync(fd);
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
function newCursor(state) {
|
|
5495
|
+
return { offset: 0, rest: Buffer.alloc(0), state };
|
|
5496
|
+
}
|
|
5497
|
+
function codexDayDirs(sessionsRoot, since) {
|
|
5498
|
+
const days = [];
|
|
5499
|
+
const end = /* @__PURE__ */ new Date();
|
|
5500
|
+
const start = since != null ? new Date(since - 24 * 36e5) : null;
|
|
5501
|
+
if (!start) {
|
|
5502
|
+
for (const y of listFiles(sessionsRoot, () => true))
|
|
5503
|
+
for (const m of listFiles(y, () => true))
|
|
5504
|
+
for (const d of listFiles(m, () => true)) days.push(d);
|
|
5505
|
+
return days;
|
|
5506
|
+
}
|
|
5507
|
+
const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
|
|
5508
|
+
while (cur <= end) {
|
|
5509
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
5510
|
+
days.push(path20.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
|
|
5511
|
+
cur.setDate(cur.getDate() + 1);
|
|
5512
|
+
}
|
|
5513
|
+
return days;
|
|
5514
|
+
}
|
|
5515
|
+
function createProgressTracker(opts) {
|
|
5516
|
+
const home = opts.homeDir ?? os5.homedir();
|
|
5517
|
+
const codexHomes = opts.codexHomes ?? (() => [path20.join(home, ".codex")]);
|
|
5518
|
+
const pollMs = opts.pollMs ?? 2e3;
|
|
5519
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5520
|
+
const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
|
|
5521
|
+
function discoverClaude(entry) {
|
|
5522
|
+
const { target } = entry;
|
|
5523
|
+
const dir = claudeProjectDir(home, target.worktreePath);
|
|
5524
|
+
for (const file of listFiles(dir, (n) => n.endsWith(".jsonl"))) {
|
|
5525
|
+
if (entry.claude.has(file) || entry.rejected.has(file)) continue;
|
|
5526
|
+
const session = path20.basename(file, ".jsonl");
|
|
5527
|
+
if (session !== target.sessionId) {
|
|
5528
|
+
const head = firstLine(file);
|
|
5529
|
+
if (head == null) continue;
|
|
5530
|
+
const started = reduceClaudeLine(initialClaudeState(), head).startedAt;
|
|
5531
|
+
if (!belongsToRun(target, started)) {
|
|
5532
|
+
entry.rejected.add(file);
|
|
5533
|
+
continue;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
entry.claude.set(file, newCursor(initialClaudeState()));
|
|
5537
|
+
}
|
|
5538
|
+
for (const file of Array.from(entry.claude.keys())) {
|
|
5539
|
+
if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
|
|
5540
|
+
const sub = path20.join(file.slice(0, -".jsonl".length), "subagents");
|
|
5541
|
+
for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
|
|
5542
|
+
if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
function discoverCodex(entry) {
|
|
5547
|
+
const { target } = entry;
|
|
5548
|
+
for (const codexHome of codexHomes()) {
|
|
5549
|
+
for (const day of codexDayDirs(path20.join(codexHome, "sessions"), target.since)) {
|
|
5550
|
+
for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
|
|
5551
|
+
if (entry.codex.has(file) || entry.rejected.has(file)) continue;
|
|
5552
|
+
const head = firstLine(file);
|
|
5553
|
+
if (head == null) continue;
|
|
5554
|
+
const meta = reduceCodexLine(initialCodexState(), head);
|
|
5555
|
+
if (!meta.cwd) continue;
|
|
5556
|
+
if (!samePath(meta.cwd, target.worktreePath) || !belongsToRun(target, meta.startedAt)) {
|
|
5557
|
+
entry.rejected.add(file);
|
|
5558
|
+
continue;
|
|
5559
|
+
}
|
|
5560
|
+
entry.codex.set(file, newCursor(initialCodexState()));
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
function drainHooks(entry) {
|
|
5566
|
+
const dir = path20.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
|
|
5567
|
+
for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
|
|
5568
|
+
try {
|
|
5569
|
+
const at = fs16.statSync(file).mtimeMs;
|
|
5570
|
+
const event = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
5571
|
+
const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
|
|
5572
|
+
const activity = foreign ? null : activityFromHook(event, at);
|
|
5573
|
+
if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
|
|
5574
|
+
} catch {
|
|
5575
|
+
continue;
|
|
5576
|
+
}
|
|
5577
|
+
try {
|
|
5578
|
+
fs16.unlinkSync(file);
|
|
5579
|
+
} catch {
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
function compose(entry) {
|
|
5584
|
+
const { target } = entry;
|
|
5585
|
+
let runUsage = emptyUsage();
|
|
5586
|
+
let primary = null;
|
|
5587
|
+
if (target.agent === "claude") {
|
|
5588
|
+
for (const [file, cursor] of entry.claude) {
|
|
5589
|
+
runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
|
|
5590
|
+
if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
|
|
5591
|
+
const isExecutor = path20.basename(file, ".jsonl") === target.sessionId;
|
|
5592
|
+
const s = cursor.state;
|
|
5593
|
+
if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
|
|
5594
|
+
primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
} else {
|
|
5598
|
+
for (const cursor of entry.codex.values()) {
|
|
5599
|
+
const s = cursor.state;
|
|
5600
|
+
runUsage = addUsage(runUsage, s.usage);
|
|
5601
|
+
if (!primary || (s.startedAt ?? 0) >= (primary.startedAt ?? 0)) {
|
|
5602
|
+
primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
if (!primary) return null;
|
|
5607
|
+
const hook = entry.hookActivity;
|
|
5608
|
+
const activity = hook?.state === "working" && primary.activity.state === "working" ? primary.activity : latestActivity(primary.activity, hook);
|
|
5609
|
+
return {
|
|
5610
|
+
todos: primary.todos,
|
|
5611
|
+
runUsage,
|
|
5612
|
+
totalUsage: addUsage(target.priorUsage ?? emptyUsage(), runUsage),
|
|
5613
|
+
...primary.contextTokens != null ? { contextTokens: primary.contextTokens } : {},
|
|
5614
|
+
activity
|
|
4961
5615
|
};
|
|
4962
|
-
settings.hooks = { PreToolUse: [taskHook], PostToolUse: [taskHook] };
|
|
4963
5616
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
5617
|
+
function sync(entry) {
|
|
5618
|
+
drainHooks(entry);
|
|
5619
|
+
if (entry.target.agent === "claude") {
|
|
5620
|
+
discoverClaude(entry);
|
|
5621
|
+
for (const [file, cursor] of entry.claude) advance(file, cursor, reduceClaudeLine, initialClaudeState);
|
|
5622
|
+
} else {
|
|
5623
|
+
discoverCodex(entry);
|
|
5624
|
+
for (const [file, cursor] of entry.codex) advance(file, cursor, reduceCodexLine, initialCodexState);
|
|
5625
|
+
}
|
|
5626
|
+
const next = compose(entry);
|
|
5627
|
+
if (!next) return null;
|
|
5628
|
+
const json = JSON.stringify(next);
|
|
5629
|
+
if (json !== entry.lastJson) {
|
|
5630
|
+
const notifications = diffProgress(entry.last, next);
|
|
5631
|
+
entry.last = next;
|
|
5632
|
+
entry.lastJson = json;
|
|
5633
|
+
opts.onUpdate(entry.target.taskId, next);
|
|
5634
|
+
if (notifications.length) opts.onNotify(entry.target.taskId, notifications);
|
|
5635
|
+
}
|
|
5636
|
+
return next;
|
|
4974
5637
|
}
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
4982
|
-
}
|
|
4983
|
-
function namespaceHash(namespace) {
|
|
4984
|
-
let hash = 2166136261;
|
|
4985
|
-
for (let i = 0; i < namespace.length; i += 1) {
|
|
4986
|
-
hash ^= namespace.charCodeAt(i);
|
|
4987
|
-
hash = Math.imul(hash, 16777619);
|
|
5638
|
+
function safeSync(entry) {
|
|
5639
|
+
try {
|
|
5640
|
+
return sync(entry);
|
|
5641
|
+
} catch {
|
|
5642
|
+
return entry.last ?? null;
|
|
5643
|
+
}
|
|
4988
5644
|
}
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
if (agent === "claude") {
|
|
4994
|
-
const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
|
|
4995
|
-
const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
|
|
4996
|
-
const modelFlag = model ? ` --model ${model}` : "";
|
|
4997
|
-
const effortFlag = effort ? ` --effort ${effort}` : "";
|
|
4998
|
-
const library = opts?.library;
|
|
4999
|
-
const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
|
|
5000
|
-
const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
|
|
5001
|
-
const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
|
|
5002
|
-
const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
|
|
5003
|
-
const tail = `${flags}${sysFlag}${promptArg}`;
|
|
5004
|
-
cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
|
|
5005
|
-
} else {
|
|
5006
|
-
const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
|
|
5007
|
-
const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
|
|
5008
|
-
const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
|
|
5009
|
-
const promptArg = combined ? ` ${shellQuote(combined)}` : "";
|
|
5010
|
-
cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
|
|
5645
|
+
function stop(entry) {
|
|
5646
|
+
clearInterval(entry.timer);
|
|
5647
|
+
if (entry.pending) clearTimeout(entry.pending);
|
|
5648
|
+
entry.watcher?.close();
|
|
5011
5649
|
}
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
}
|
|
5015
|
-
var AGENT_NAMES = {
|
|
5016
|
-
claude: "Claude Code",
|
|
5017
|
-
codex: "Codex CLI"
|
|
5018
|
-
};
|
|
5019
|
-
var DEFAULT_MODELS = {
|
|
5020
|
-
claude: "sonnet",
|
|
5021
|
-
codex: "gpt-5.5"
|
|
5022
|
-
};
|
|
5023
|
-
var LEGACY_MODEL_FALLBACKS = {
|
|
5024
|
-
codex: {
|
|
5025
|
-
"gpt-5-codex": "gpt-5.5",
|
|
5026
|
-
"gpt-5": "gpt-5.5"
|
|
5650
|
+
function sameTarget(a, b) {
|
|
5651
|
+
return a.agent === b.agent && a.worktreePath === b.worktreePath && a.sessionId === b.sessionId && a.since === b.since && JSON.stringify(a.priorUsage ?? null) === JSON.stringify(b.priorUsage ?? null);
|
|
5027
5652
|
}
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5653
|
+
return {
|
|
5654
|
+
track(target) {
|
|
5655
|
+
const existing = entries.get(target.taskId);
|
|
5656
|
+
if (existing && sameTarget(existing.target, target)) return;
|
|
5657
|
+
if (existing) stop(existing);
|
|
5658
|
+
const entry = {
|
|
5659
|
+
target,
|
|
5660
|
+
claude: /* @__PURE__ */ new Map(),
|
|
5661
|
+
codex: /* @__PURE__ */ new Map(),
|
|
5662
|
+
rejected: /* @__PURE__ */ new Set(),
|
|
5663
|
+
hookActivity: null,
|
|
5664
|
+
timer: setInterval(() => safeSync(entry), pollMs),
|
|
5665
|
+
watcher: null,
|
|
5666
|
+
pending: null
|
|
5667
|
+
};
|
|
5668
|
+
entry.timer.unref?.();
|
|
5669
|
+
entries.set(target.taskId, entry);
|
|
5670
|
+
const eventsDir = path20.join(target.worktreePath, PROGRESS_EVENTS_DIR);
|
|
5671
|
+
try {
|
|
5672
|
+
fs16.mkdirSync(eventsDir, { recursive: true });
|
|
5673
|
+
entry.watcher = fs16.watch(eventsDir, () => {
|
|
5674
|
+
if (entry.pending) return;
|
|
5675
|
+
entry.pending = setTimeout(() => {
|
|
5676
|
+
entry.pending = null;
|
|
5677
|
+
safeSync(entry);
|
|
5678
|
+
}, 50);
|
|
5679
|
+
});
|
|
5680
|
+
entry.watcher.on("error", () => entry.watcher?.close());
|
|
5681
|
+
entry.watcher.unref?.();
|
|
5682
|
+
} catch {
|
|
5683
|
+
}
|
|
5684
|
+
safeSync(entry);
|
|
5685
|
+
},
|
|
5686
|
+
untrack(taskId) {
|
|
5687
|
+
const entry = entries.get(taskId);
|
|
5688
|
+
if (!entry) return;
|
|
5689
|
+
stop(entry);
|
|
5690
|
+
entries.delete(taskId);
|
|
5691
|
+
},
|
|
5692
|
+
untrackAll() {
|
|
5693
|
+
for (const entry of entries.values()) stop(entry);
|
|
5694
|
+
entries.clear();
|
|
5695
|
+
},
|
|
5696
|
+
snapshot(taskId) {
|
|
5697
|
+
const entry = entries.get(taskId);
|
|
5698
|
+
return entry ? safeSync(entry) : null;
|
|
5699
|
+
},
|
|
5700
|
+
isTracking(taskId) {
|
|
5701
|
+
return entries.has(taskId);
|
|
5702
|
+
}
|
|
5703
|
+
};
|
|
5062
5704
|
}
|
|
5063
5705
|
|
|
5064
5706
|
// packages/core/src/service.ts
|
|
@@ -5113,8 +5755,61 @@ function validBoard(value) {
|
|
|
5113
5755
|
}
|
|
5114
5756
|
return next;
|
|
5115
5757
|
}
|
|
5116
|
-
function createCore({ sessions, bus, version }) {
|
|
5758
|
+
function createCore({ sessions, bus, version, homeDir }) {
|
|
5117
5759
|
const sink = bus.sink();
|
|
5760
|
+
let libraryCodexHome = null;
|
|
5761
|
+
void libraryRoot().then((root) => {
|
|
5762
|
+
libraryCodexHome = libraryPaths(root).codexHome;
|
|
5763
|
+
}).catch(() => {
|
|
5764
|
+
});
|
|
5765
|
+
const home = homeDir ?? os6.homedir();
|
|
5766
|
+
const progress = createProgressTracker({
|
|
5767
|
+
homeDir: home,
|
|
5768
|
+
codexHomes: () => [path21.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
|
|
5769
|
+
onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
|
|
5770
|
+
onNotify: (taskId, list) => {
|
|
5771
|
+
const prefs = resolveNotificationSettings(getSettings());
|
|
5772
|
+
if (!prefs.enabled) return;
|
|
5773
|
+
const wanted = {
|
|
5774
|
+
step_completed: prefs.stepCompleted,
|
|
5775
|
+
all_completed: prefs.allCompleted,
|
|
5776
|
+
waiting_input: prefs.waitingInput
|
|
5777
|
+
};
|
|
5778
|
+
const notifications = list.filter((n) => wanted[n.kind]);
|
|
5779
|
+
const task = findTask(taskId);
|
|
5780
|
+
if (!task || !notifications.length) return;
|
|
5781
|
+
bus.emit("progress:notify", { taskId, title: task.title, notifications });
|
|
5782
|
+
}
|
|
5783
|
+
});
|
|
5784
|
+
function progressTarget(task) {
|
|
5785
|
+
if (!task.worktreePath) return null;
|
|
5786
|
+
const agent = taskAgent(task);
|
|
5787
|
+
return {
|
|
5788
|
+
taskId: task.id,
|
|
5789
|
+
agent,
|
|
5790
|
+
worktreePath: task.worktreePath,
|
|
5791
|
+
sessionId: agent === "claude" ? executorSessionId(task.id, task.runId) : void 0,
|
|
5792
|
+
since: task.launchedAt,
|
|
5793
|
+
priorUsage: task.usage
|
|
5794
|
+
};
|
|
5795
|
+
}
|
|
5796
|
+
function trackProgress(task) {
|
|
5797
|
+
const target = progressTarget(task);
|
|
5798
|
+
if (target) progress.track(target);
|
|
5799
|
+
}
|
|
5800
|
+
function usageThroughCurrentRun(task) {
|
|
5801
|
+
const target = progressTarget(task);
|
|
5802
|
+
if (!target) return task.usage;
|
|
5803
|
+
const wasTracking = progress.isTracking(task.id);
|
|
5804
|
+
progress.track(target);
|
|
5805
|
+
const snapshot = progress.snapshot(task.id);
|
|
5806
|
+
if (!wasTracking) progress.untrack(task.id);
|
|
5807
|
+
return snapshot?.totalUsage ?? task.usage;
|
|
5808
|
+
}
|
|
5809
|
+
try {
|
|
5810
|
+
for (const task of getState().board.in_progress) trackProgress(task);
|
|
5811
|
+
} catch {
|
|
5812
|
+
}
|
|
5118
5813
|
async function teardownSession(key) {
|
|
5119
5814
|
await sessions.kill(key);
|
|
5120
5815
|
unwatchSubAgents(key);
|
|
@@ -5153,13 +5848,13 @@ function createCore({ sessions, bus, version }) {
|
|
|
5153
5848
|
}
|
|
5154
5849
|
function claudeSessionFile(cwd, sessionId) {
|
|
5155
5850
|
const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
5156
|
-
return
|
|
5851
|
+
return path21.join(os6.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
|
|
5157
5852
|
}
|
|
5158
5853
|
async function removeTaskWorktree(task) {
|
|
5159
5854
|
if (!task.projectPath || !task.worktreePath) return;
|
|
5160
5855
|
const branch = task.branch || fallbackBranchName(task.id);
|
|
5161
5856
|
await removeWorktree(task.projectPath, task.worktreePath);
|
|
5162
|
-
deleteArtifacts(task.workspacePath ??
|
|
5857
|
+
deleteArtifacts(task.workspacePath ?? path21.dirname(task.worktreePath), task.worktreePath);
|
|
5163
5858
|
await deleteBranch(task.projectPath, branch);
|
|
5164
5859
|
}
|
|
5165
5860
|
function columnOf(taskId) {
|
|
@@ -5204,6 +5899,17 @@ function createCore({ sessions, bus, version }) {
|
|
|
5204
5899
|
let folderDialog = null;
|
|
5205
5900
|
const handlers = {
|
|
5206
5901
|
"vibeflow:getState": () => getState(),
|
|
5902
|
+
// A card's progress right now (todo list, usage, activity), for a frontend
|
|
5903
|
+
// that just connected; 'progress:update' carries every later change. Null
|
|
5904
|
+
// when the card has no transcript yet or is not running.
|
|
5905
|
+
"progress:get": (taskId) => {
|
|
5906
|
+
const task = requireTask(taskId);
|
|
5907
|
+
if (!progress.isTracking(task.id)) {
|
|
5908
|
+
if (columnOf(task.id) !== "in_progress") return null;
|
|
5909
|
+
trackProgress(task);
|
|
5910
|
+
}
|
|
5911
|
+
return progress.snapshot(task.id);
|
|
5912
|
+
},
|
|
5207
5913
|
"app:getVersion": () => version,
|
|
5208
5914
|
"vibeflow:setBoard": (board) => {
|
|
5209
5915
|
setBoard(validBoard(board));
|
|
@@ -5238,8 +5944,8 @@ function createCore({ sessions, bus, version }) {
|
|
|
5238
5944
|
"projects:listRecent": () => listRecentProjects(getStore()),
|
|
5239
5945
|
// `vibeflow open <path>` while a host runs: only the host writes the store.
|
|
5240
5946
|
"projects:record": (projectPath) => {
|
|
5241
|
-
const p =
|
|
5242
|
-
if (!
|
|
5947
|
+
const p = path21.resolve(str(projectPath, "projectPath"));
|
|
5948
|
+
if (!fs17.existsSync(p) || !fs17.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
|
|
5243
5949
|
recordRecentProject(getStore(), p);
|
|
5244
5950
|
return listRecentProjects(getStore());
|
|
5245
5951
|
},
|
|
@@ -5266,7 +5972,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5266
5972
|
return { state: getState(), task };
|
|
5267
5973
|
},
|
|
5268
5974
|
"vibeflow:removeTask": (taskId) => {
|
|
5269
|
-
|
|
5975
|
+
const id = requireTask(taskId).id;
|
|
5976
|
+
progress.untrack(id);
|
|
5977
|
+
removeTask(id);
|
|
5270
5978
|
return getState();
|
|
5271
5979
|
},
|
|
5272
5980
|
// Discard everything the last run left behind — the PTY session, the
|
|
@@ -5277,6 +5985,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5277
5985
|
const task = requireTask(taskId);
|
|
5278
5986
|
if (!task.worktreePath) {
|
|
5279
5987
|
await teardownTask(task.id);
|
|
5988
|
+
progress.untrack(task.id);
|
|
5280
5989
|
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
|
|
5281
5990
|
const reset2 = findTask(task.id);
|
|
5282
5991
|
if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
@@ -5285,11 +5994,13 @@ function createCore({ sessions, bus, version }) {
|
|
|
5285
5994
|
if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
|
|
5286
5995
|
await teardownTask(task.id);
|
|
5287
5996
|
await resetWorktreeToBase(task.worktreePath, task.baseBranch);
|
|
5288
|
-
const workspacePath = task.workspacePath ??
|
|
5997
|
+
const workspacePath = task.workspacePath ?? path21.dirname(task.worktreePath);
|
|
5289
5998
|
deleteArtifacts(workspacePath, task.worktreePath);
|
|
5290
5999
|
deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5291
6000
|
resetSubAgents(task.worktreePath);
|
|
5292
|
-
|
|
6001
|
+
const usage = usageThroughCurrentRun(task);
|
|
6002
|
+
progress.untrack(task.id);
|
|
6003
|
+
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4(), ...usage ? { usage } : {} });
|
|
5293
6004
|
const reset = findTask(task.id);
|
|
5294
6005
|
if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
5295
6006
|
bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
|
|
@@ -5326,7 +6037,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5326
6037
|
}
|
|
5327
6038
|
const info = await getGitInfo(nextProject);
|
|
5328
6039
|
if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
|
|
5329
|
-
const projectName =
|
|
6040
|
+
const projectName = path21.basename(nextProject);
|
|
5330
6041
|
const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
|
|
5331
6042
|
await teardownTask(existing.id);
|
|
5332
6043
|
await removeTaskWorktree(existing);
|
|
@@ -5389,6 +6100,10 @@ function createCore({ sessions, bus, version }) {
|
|
|
5389
6100
|
watchSubAgents(key, cwd, (subAgents) => {
|
|
5390
6101
|
bus.emit("subagents:update", { taskId: task.id, subAgents });
|
|
5391
6102
|
});
|
|
6103
|
+
if (p.launch) {
|
|
6104
|
+
const current = findTask(task.id);
|
|
6105
|
+
if (current) trackProgress(current);
|
|
6106
|
+
}
|
|
5392
6107
|
return result;
|
|
5393
6108
|
},
|
|
5394
6109
|
// Join a session as it is: no restart, for a frontend that attaches
|
|
@@ -5416,7 +6131,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5416
6131
|
"claude:sessionExists": (taskId) => {
|
|
5417
6132
|
const task = requireTask(taskId);
|
|
5418
6133
|
if (!task.worktreePath) return false;
|
|
5419
|
-
return
|
|
6134
|
+
return fs17.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
|
|
5420
6135
|
},
|
|
5421
6136
|
"sessions:list": async () => {
|
|
5422
6137
|
const keys = await sessions.list();
|
|
@@ -5463,7 +6178,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5463
6178
|
const task = requireTask(taskId);
|
|
5464
6179
|
if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
|
|
5465
6180
|
const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
|
|
5466
|
-
if (!
|
|
6181
|
+
if (!fs17.existsSync(dir)) return "artifacts directory does not exist yet";
|
|
5467
6182
|
return getPlatform().openPath(dir);
|
|
5468
6183
|
},
|
|
5469
6184
|
"task:getSpecs": async (taskId) => {
|
|
@@ -5573,24 +6288,31 @@ function createCore({ sessions, bus, version }) {
|
|
|
5573
6288
|
if (task.worktreePath) {
|
|
5574
6289
|
outcome = await captureTaskOutcome(task.worktreePath, task.baseBranch ?? "main") ?? void 0;
|
|
5575
6290
|
}
|
|
6291
|
+
const usage = usageThroughCurrentRun(task);
|
|
6292
|
+
progress.untrack(task.id);
|
|
5576
6293
|
if (task.projectPath && task.worktreePath) {
|
|
5577
6294
|
await removeTaskWorktree(task);
|
|
5578
6295
|
await syncBaseBranch(task.projectPath, task.baseBranch ?? "main");
|
|
5579
6296
|
}
|
|
5580
|
-
updateTask(task.id,
|
|
6297
|
+
updateTask(task.id, {
|
|
6298
|
+
worktreePath: void 0,
|
|
6299
|
+
...outcome ? { outcome } : {},
|
|
6300
|
+
...usage ? { usage } : {}
|
|
6301
|
+
});
|
|
5581
6302
|
return getState();
|
|
5582
6303
|
},
|
|
5583
6304
|
// Delete: cleanup (PTY + worktree) AND drop the card from the board.
|
|
5584
6305
|
"vibeflow:deleteTask": async (taskId) => {
|
|
5585
6306
|
const task = requireTask(taskId);
|
|
5586
6307
|
await teardownTask(task.id);
|
|
6308
|
+
progress.untrack(task.id);
|
|
5587
6309
|
cancelChatSend(task.id);
|
|
5588
6310
|
if (task.workspacePath) {
|
|
5589
6311
|
deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5590
6312
|
}
|
|
5591
6313
|
await removeTaskWorktree(task);
|
|
5592
6314
|
if (task.workspacePath) {
|
|
5593
|
-
|
|
6315
|
+
fs17.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
|
|
5594
6316
|
}
|
|
5595
6317
|
clearConversation(task.id);
|
|
5596
6318
|
removeTask(task.id);
|
|
@@ -5643,9 +6365,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5643
6365
|
// Watch the containing directory, not the file: JsonStore renames a temp
|
|
5644
6366
|
// file over the target and so replaces the inode on every save.
|
|
5645
6367
|
watchStore() {
|
|
5646
|
-
const file =
|
|
6368
|
+
const file = path21.basename(getStorePath());
|
|
5647
6369
|
let debounce = null;
|
|
5648
|
-
storeWatcher =
|
|
6370
|
+
storeWatcher = fs17.watch(path21.dirname(getStorePath()), (_event, filename) => {
|
|
5649
6371
|
if (filename !== file) return;
|
|
5650
6372
|
if (debounce) clearTimeout(debounce);
|
|
5651
6373
|
debounce = setTimeout(() => {
|
|
@@ -5661,6 +6383,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5661
6383
|
sessions.shutdown();
|
|
5662
6384
|
cancelAllChatSends();
|
|
5663
6385
|
unwatchAllSubAgents();
|
|
6386
|
+
progress.untrackAll();
|
|
5664
6387
|
storeWatcher?.close();
|
|
5665
6388
|
}
|
|
5666
6389
|
};
|
|
@@ -5677,9 +6400,9 @@ function createSessionBackend(sink, choice) {
|
|
|
5677
6400
|
}
|
|
5678
6401
|
|
|
5679
6402
|
// packages/core/src/web-server.ts
|
|
5680
|
-
import
|
|
6403
|
+
import fs18 from "fs";
|
|
5681
6404
|
import http from "http";
|
|
5682
|
-
import
|
|
6405
|
+
import path22 from "path";
|
|
5683
6406
|
import { timingSafeEqual } from "crypto";
|
|
5684
6407
|
import { WebSocketServer } from "ws";
|
|
5685
6408
|
var TOKEN_COOKIE = "vf_token";
|
|
@@ -5732,14 +6455,14 @@ function resolveStatic(root, urlPath) {
|
|
|
5732
6455
|
return null;
|
|
5733
6456
|
}
|
|
5734
6457
|
if (decoded.includes("\0")) return null;
|
|
5735
|
-
const rel =
|
|
5736
|
-
const base =
|
|
5737
|
-
const candidates = [rel,
|
|
6458
|
+
const rel = path22.posix.normalize(decoded).replace(/^\/+/, "");
|
|
6459
|
+
const base = path22.resolve(root);
|
|
6460
|
+
const candidates = [rel, path22.posix.join(rel, "index.html"), `${rel}.html`];
|
|
5738
6461
|
for (const c of candidates) {
|
|
5739
|
-
const full =
|
|
5740
|
-
if (full !== base && !full.startsWith(base +
|
|
6462
|
+
const full = path22.resolve(base, c);
|
|
6463
|
+
if (full !== base && !full.startsWith(base + path22.sep)) return null;
|
|
5741
6464
|
try {
|
|
5742
|
-
if (
|
|
6465
|
+
if (fs18.statSync(full).isFile()) return full;
|
|
5743
6466
|
} catch {
|
|
5744
6467
|
}
|
|
5745
6468
|
}
|
|
@@ -5800,7 +6523,7 @@ async function startWebServer(options) {
|
|
|
5800
6523
|
send(res, 404, "Not found");
|
|
5801
6524
|
return;
|
|
5802
6525
|
}
|
|
5803
|
-
const ext =
|
|
6526
|
+
const ext = path22.extname(file).toLowerCase();
|
|
5804
6527
|
const immutable = url.pathname.startsWith("/_next/static/");
|
|
5805
6528
|
res.writeHead(200, {
|
|
5806
6529
|
"Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
|
|
@@ -5813,7 +6536,7 @@ async function startWebServer(options) {
|
|
|
5813
6536
|
res.end();
|
|
5814
6537
|
return;
|
|
5815
6538
|
}
|
|
5816
|
-
|
|
6539
|
+
fs18.createReadStream(file).pipe(res);
|
|
5817
6540
|
}
|
|
5818
6541
|
const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
|
|
5819
6542
|
function reject(socket, status, reason) {
|
|
@@ -5985,7 +6708,7 @@ function loginUrl(lock, pathname = "/home/") {
|
|
|
5985
6708
|
return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
|
|
5986
6709
|
}
|
|
5987
6710
|
function isWebDir(dir) {
|
|
5988
|
-
return
|
|
6711
|
+
return fs19.existsSync(path23.join(dir, "home", "index.html"));
|
|
5989
6712
|
}
|
|
5990
6713
|
|
|
5991
6714
|
// packages/cli/src/remote.ts
|
|
@@ -6102,8 +6825,8 @@ function connectToHost(lock) {
|
|
|
6102
6825
|
}
|
|
6103
6826
|
|
|
6104
6827
|
// packages/cli/src/doctor.ts
|
|
6105
|
-
import
|
|
6106
|
-
import
|
|
6828
|
+
import fs20 from "fs";
|
|
6829
|
+
import path24 from "path";
|
|
6107
6830
|
import { execFile as execFile8 } from "child_process";
|
|
6108
6831
|
import { promisify as promisify5 } from "util";
|
|
6109
6832
|
init_env();
|
|
@@ -6243,10 +6966,10 @@ async function runDoctor(options) {
|
|
|
6243
6966
|
});
|
|
6244
6967
|
}
|
|
6245
6968
|
try {
|
|
6246
|
-
|
|
6247
|
-
const probe =
|
|
6248
|
-
|
|
6249
|
-
|
|
6969
|
+
fs20.mkdirSync(options.userDataDir, { recursive: true });
|
|
6970
|
+
const probe = path24.join(options.userDataDir, `.doctor-${process.pid}`);
|
|
6971
|
+
fs20.writeFileSync(probe, "");
|
|
6972
|
+
fs20.rmSync(probe);
|
|
6250
6973
|
checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
|
|
6251
6974
|
} catch (err) {
|
|
6252
6975
|
checks.push({
|
|
@@ -6300,24 +7023,24 @@ Options:
|
|
|
6300
7023
|
`.trim();
|
|
6301
7024
|
function detectLayout() {
|
|
6302
7025
|
const file = fileURLToPath(import.meta.url);
|
|
6303
|
-
const here =
|
|
6304
|
-
const bundledWeb =
|
|
6305
|
-
if (isWebDir(bundledWeb) ||
|
|
7026
|
+
const here = path27.dirname(file);
|
|
7027
|
+
const bundledWeb = path27.join(here, "web");
|
|
7028
|
+
if (isWebDir(bundledWeb) || path27.basename(here) === "dist") {
|
|
6306
7029
|
return {
|
|
6307
|
-
version: readVersion(
|
|
7030
|
+
version: readVersion(path27.join(here, "..", "package.json")),
|
|
6308
7031
|
webDir: isWebDir(bundledWeb) ? bundledWeb : null,
|
|
6309
7032
|
sourceRoot: null,
|
|
6310
7033
|
cliEntry: file
|
|
6311
7034
|
};
|
|
6312
7035
|
}
|
|
6313
|
-
const root =
|
|
7036
|
+
const root = path27.resolve(here, "..", "..", "..");
|
|
6314
7037
|
const override = process.env.VIBEFLOW_WEB_DIR;
|
|
6315
|
-
const webDir = override && isWebDir(override) ? override : isWebDir(
|
|
6316
|
-
return { version: readVersion(
|
|
7038
|
+
const webDir = override && isWebDir(override) ? override : isWebDir(path27.join(root, "app")) ? path27.join(root, "app") : null;
|
|
7039
|
+
return { version: readVersion(path27.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
|
|
6317
7040
|
}
|
|
6318
7041
|
function readVersion(pkgPath) {
|
|
6319
7042
|
try {
|
|
6320
|
-
return JSON.parse(
|
|
7043
|
+
return JSON.parse(fs22.readFileSync(pkgPath, "utf8")).version;
|
|
6321
7044
|
} catch {
|
|
6322
7045
|
return "0.0.0";
|
|
6323
7046
|
}
|
|
@@ -6523,8 +7246,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
|
|
|
6523
7246
|
console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
|
|
6524
7247
|
return 1;
|
|
6525
7248
|
}
|
|
6526
|
-
const dir =
|
|
6527
|
-
if (!
|
|
7249
|
+
const dir = path27.resolve(target);
|
|
7250
|
+
if (!fs22.existsSync(dir) || !fs22.statSync(dir).isDirectory()) {
|
|
6528
7251
|
console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
|
|
6529
7252
|
return 1;
|
|
6530
7253
|
}
|