@kw-yeh/vibeflow 4.5.1 → 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/README.md +3 -3
- package/dist/vibeflow.mjs +1046 -320
- package/dist/web/404/index.html +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/{2iwmsskDbEWs3K3yxNplb → 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/{2iwmsskDbEWs3K3yxNplb → 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) => {
|
|
@@ -3465,7 +3995,8 @@ async function provisionTaskWorktree(task) {
|
|
|
3465
3995
|
branch: result.branch,
|
|
3466
3996
|
worktreePath: result.worktreePath,
|
|
3467
3997
|
baseBranch: result.baseBranch,
|
|
3468
|
-
pushed: result.pushed
|
|
3998
|
+
pushed: result.pushed,
|
|
3999
|
+
provisionedAt: Date.now()
|
|
3469
4000
|
};
|
|
3470
4001
|
} catch (err) {
|
|
3471
4002
|
throw Object.assign(
|
|
@@ -3713,8 +4244,8 @@ async function taskCommand(argv) {
|
|
|
3713
4244
|
}
|
|
3714
4245
|
|
|
3715
4246
|
// packages/cli/src/host.ts
|
|
3716
|
-
import
|
|
3717
|
-
import
|
|
4247
|
+
import fs19 from "fs";
|
|
4248
|
+
import path23 from "path";
|
|
3718
4249
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3719
4250
|
|
|
3720
4251
|
// packages/core/src/events.ts
|
|
@@ -4252,9 +4783,9 @@ async function syncBuiltinsAtStartup() {
|
|
|
4252
4783
|
}
|
|
4253
4784
|
|
|
4254
4785
|
// packages/core/src/service.ts
|
|
4255
|
-
import
|
|
4256
|
-
import
|
|
4257
|
-
import
|
|
4786
|
+
import fs17 from "fs";
|
|
4787
|
+
import os6 from "os";
|
|
4788
|
+
import path21 from "path";
|
|
4258
4789
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4259
4790
|
|
|
4260
4791
|
// packages/core/src/agent-models.ts
|
|
@@ -4888,176 +5419,288 @@ function startChatSend(opts, sender) {
|
|
|
4888
5419
|
// packages/core/src/service.ts
|
|
4889
5420
|
init_attachments();
|
|
4890
5421
|
|
|
4891
|
-
// packages/core/src/
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
return
|
|
4899
|
-
}
|
|
4900
|
-
function
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
|
|
4907
|
-
return [
|
|
4908
|
-
"Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
|
|
4909
|
-
`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`,
|
|
4910
|
-
`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`,
|
|
4911
|
-
`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`,
|
|
4912
|
-
`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`,
|
|
4913
|
-
"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"
|
|
4914
|
-
].join("\n");
|
|
4915
|
-
}
|
|
4916
|
-
var AUTO_PERMISSION_MODE = "auto";
|
|
4917
|
-
function claudePermissionFlag(autoMode) {
|
|
4918
|
-
return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
|
|
4919
|
-
}
|
|
4920
|
-
function shellQuote(s) {
|
|
4921
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
4922
|
-
}
|
|
4923
|
-
function toShellPath(p) {
|
|
4924
|
-
return p.replace(/\\/g, "/");
|
|
4925
|
-
}
|
|
4926
|
-
function boardEnvPrefix(task, opts) {
|
|
4927
|
-
const board = opts?.boardCli;
|
|
4928
|
-
if (!board) return "";
|
|
4929
|
-
const entries = [
|
|
4930
|
-
["VIBEFLOW_TASK_ID", task.id],
|
|
4931
|
-
["VIBEFLOW_PROJECT_PATH", task.projectPath],
|
|
4932
|
-
["VIBEFLOW_BRANCH", task.branch],
|
|
4933
|
-
["VIBEFLOW_BASE_BRANCH", task.baseBranch],
|
|
4934
|
-
["VIBEFLOW_STORE_DIR", board?.storeDir],
|
|
4935
|
-
["VIBEFLOW_CLI", board?.cliPath],
|
|
4936
|
-
["VIBEFLOW_CLI_LOADER", board?.loaderPath],
|
|
4937
|
-
["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
|
|
4938
|
-
];
|
|
4939
|
-
const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
|
|
4940
|
-
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);
|
|
4941
5437
|
}
|
|
4942
|
-
function
|
|
4943
|
-
|
|
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
|
+
}
|
|
4944
5444
|
}
|
|
4945
|
-
function
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
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
|
+
}
|
|
4950
5459
|
}
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
|
4960
5615
|
};
|
|
4961
|
-
settings.hooks = { PreToolUse: [taskHook], PostToolUse: [taskHook] };
|
|
4962
5616
|
}
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
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;
|
|
4973
5637
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
4981
|
-
}
|
|
4982
|
-
function namespaceHash(namespace) {
|
|
4983
|
-
let hash = 2166136261;
|
|
4984
|
-
for (let i = 0; i < namespace.length; i += 1) {
|
|
4985
|
-
hash ^= namespace.charCodeAt(i);
|
|
4986
|
-
hash = Math.imul(hash, 16777619);
|
|
5638
|
+
function safeSync(entry) {
|
|
5639
|
+
try {
|
|
5640
|
+
return sync(entry);
|
|
5641
|
+
} catch {
|
|
5642
|
+
return entry.last ?? null;
|
|
5643
|
+
}
|
|
4987
5644
|
}
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
if (agent === "claude") {
|
|
4993
|
-
const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
|
|
4994
|
-
const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
|
|
4995
|
-
const modelFlag = model ? ` --model ${model}` : "";
|
|
4996
|
-
const effortFlag = effort ? ` --effort ${effort}` : "";
|
|
4997
|
-
const library = opts?.library;
|
|
4998
|
-
const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
|
|
4999
|
-
const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
|
|
5000
|
-
const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
|
|
5001
|
-
const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
|
|
5002
|
-
const tail = `${flags}${sysFlag}${promptArg}`;
|
|
5003
|
-
cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
|
|
5004
|
-
} else {
|
|
5005
|
-
const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
|
|
5006
|
-
const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
|
|
5007
|
-
const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
|
|
5008
|
-
const promptArg = combined ? ` ${shellQuote(combined)}` : "";
|
|
5009
|
-
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();
|
|
5010
5649
|
}
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
}
|
|
5014
|
-
var AGENT_NAMES = {
|
|
5015
|
-
claude: "Claude Code",
|
|
5016
|
-
codex: "Codex CLI"
|
|
5017
|
-
};
|
|
5018
|
-
var DEFAULT_MODELS = {
|
|
5019
|
-
claude: "sonnet",
|
|
5020
|
-
codex: "gpt-5.5"
|
|
5021
|
-
};
|
|
5022
|
-
var LEGACY_MODEL_FALLBACKS = {
|
|
5023
|
-
codex: {
|
|
5024
|
-
"gpt-5-codex": "gpt-5.5",
|
|
5025
|
-
"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);
|
|
5026
5652
|
}
|
|
5027
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
5061
5704
|
}
|
|
5062
5705
|
|
|
5063
5706
|
// packages/core/src/service.ts
|
|
@@ -5112,8 +5755,61 @@ function validBoard(value) {
|
|
|
5112
5755
|
}
|
|
5113
5756
|
return next;
|
|
5114
5757
|
}
|
|
5115
|
-
function createCore({ sessions, bus, version }) {
|
|
5758
|
+
function createCore({ sessions, bus, version, homeDir }) {
|
|
5116
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
|
+
}
|
|
5117
5813
|
async function teardownSession(key) {
|
|
5118
5814
|
await sessions.kill(key);
|
|
5119
5815
|
unwatchSubAgents(key);
|
|
@@ -5152,13 +5848,13 @@ function createCore({ sessions, bus, version }) {
|
|
|
5152
5848
|
}
|
|
5153
5849
|
function claudeSessionFile(cwd, sessionId) {
|
|
5154
5850
|
const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
5155
|
-
return
|
|
5851
|
+
return path21.join(os6.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
|
|
5156
5852
|
}
|
|
5157
5853
|
async function removeTaskWorktree(task) {
|
|
5158
5854
|
if (!task.projectPath || !task.worktreePath) return;
|
|
5159
5855
|
const branch = task.branch || fallbackBranchName(task.id);
|
|
5160
5856
|
await removeWorktree(task.projectPath, task.worktreePath);
|
|
5161
|
-
deleteArtifacts(task.workspacePath ??
|
|
5857
|
+
deleteArtifacts(task.workspacePath ?? path21.dirname(task.worktreePath), task.worktreePath);
|
|
5162
5858
|
await deleteBranch(task.projectPath, branch);
|
|
5163
5859
|
}
|
|
5164
5860
|
function columnOf(taskId) {
|
|
@@ -5203,6 +5899,17 @@ function createCore({ sessions, bus, version }) {
|
|
|
5203
5899
|
let folderDialog = null;
|
|
5204
5900
|
const handlers = {
|
|
5205
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
|
+
},
|
|
5206
5913
|
"app:getVersion": () => version,
|
|
5207
5914
|
"vibeflow:setBoard": (board) => {
|
|
5208
5915
|
setBoard(validBoard(board));
|
|
@@ -5237,8 +5944,8 @@ function createCore({ sessions, bus, version }) {
|
|
|
5237
5944
|
"projects:listRecent": () => listRecentProjects(getStore()),
|
|
5238
5945
|
// `vibeflow open <path>` while a host runs: only the host writes the store.
|
|
5239
5946
|
"projects:record": (projectPath) => {
|
|
5240
|
-
const p =
|
|
5241
|
-
if (!
|
|
5947
|
+
const p = path21.resolve(str(projectPath, "projectPath"));
|
|
5948
|
+
if (!fs17.existsSync(p) || !fs17.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
|
|
5242
5949
|
recordRecentProject(getStore(), p);
|
|
5243
5950
|
return listRecentProjects(getStore());
|
|
5244
5951
|
},
|
|
@@ -5265,7 +5972,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5265
5972
|
return { state: getState(), task };
|
|
5266
5973
|
},
|
|
5267
5974
|
"vibeflow:removeTask": (taskId) => {
|
|
5268
|
-
|
|
5975
|
+
const id = requireTask(taskId).id;
|
|
5976
|
+
progress.untrack(id);
|
|
5977
|
+
removeTask(id);
|
|
5269
5978
|
return getState();
|
|
5270
5979
|
},
|
|
5271
5980
|
// Discard everything the last run left behind — the PTY session, the
|
|
@@ -5276,6 +5985,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5276
5985
|
const task = requireTask(taskId);
|
|
5277
5986
|
if (!task.worktreePath) {
|
|
5278
5987
|
await teardownTask(task.id);
|
|
5988
|
+
progress.untrack(task.id);
|
|
5279
5989
|
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
|
|
5280
5990
|
const reset2 = findTask(task.id);
|
|
5281
5991
|
if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
@@ -5284,11 +5994,13 @@ function createCore({ sessions, bus, version }) {
|
|
|
5284
5994
|
if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
|
|
5285
5995
|
await teardownTask(task.id);
|
|
5286
5996
|
await resetWorktreeToBase(task.worktreePath, task.baseBranch);
|
|
5287
|
-
const workspacePath = task.workspacePath ??
|
|
5997
|
+
const workspacePath = task.workspacePath ?? path21.dirname(task.worktreePath);
|
|
5288
5998
|
deleteArtifacts(workspacePath, task.worktreePath);
|
|
5289
5999
|
deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5290
6000
|
resetSubAgents(task.worktreePath);
|
|
5291
|
-
|
|
6001
|
+
const usage = usageThroughCurrentRun(task);
|
|
6002
|
+
progress.untrack(task.id);
|
|
6003
|
+
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4(), ...usage ? { usage } : {} });
|
|
5292
6004
|
const reset = findTask(task.id);
|
|
5293
6005
|
if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
5294
6006
|
bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
|
|
@@ -5325,7 +6037,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5325
6037
|
}
|
|
5326
6038
|
const info = await getGitInfo(nextProject);
|
|
5327
6039
|
if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
|
|
5328
|
-
const projectName =
|
|
6040
|
+
const projectName = path21.basename(nextProject);
|
|
5329
6041
|
const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
|
|
5330
6042
|
await teardownTask(existing.id);
|
|
5331
6043
|
await removeTaskWorktree(existing);
|
|
@@ -5339,6 +6051,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5339
6051
|
...branchChanged ? { branch: nextBranch, branchExplicit: true } : {},
|
|
5340
6052
|
worktreePath: void 0,
|
|
5341
6053
|
pushed: void 0,
|
|
6054
|
+
provisionedAt: void 0,
|
|
5342
6055
|
launchedAt: void 0
|
|
5343
6056
|
};
|
|
5344
6057
|
}
|
|
@@ -5387,6 +6100,10 @@ function createCore({ sessions, bus, version }) {
|
|
|
5387
6100
|
watchSubAgents(key, cwd, (subAgents) => {
|
|
5388
6101
|
bus.emit("subagents:update", { taskId: task.id, subAgents });
|
|
5389
6102
|
});
|
|
6103
|
+
if (p.launch) {
|
|
6104
|
+
const current = findTask(task.id);
|
|
6105
|
+
if (current) trackProgress(current);
|
|
6106
|
+
}
|
|
5390
6107
|
return result;
|
|
5391
6108
|
},
|
|
5392
6109
|
// Join a session as it is: no restart, for a frontend that attaches
|
|
@@ -5414,7 +6131,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5414
6131
|
"claude:sessionExists": (taskId) => {
|
|
5415
6132
|
const task = requireTask(taskId);
|
|
5416
6133
|
if (!task.worktreePath) return false;
|
|
5417
|
-
return
|
|
6134
|
+
return fs17.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
|
|
5418
6135
|
},
|
|
5419
6136
|
"sessions:list": async () => {
|
|
5420
6137
|
const keys = await sessions.list();
|
|
@@ -5461,16 +6178,17 @@ function createCore({ sessions, bus, version }) {
|
|
|
5461
6178
|
const task = requireTask(taskId);
|
|
5462
6179
|
if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
|
|
5463
6180
|
const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
|
|
5464
|
-
if (!
|
|
6181
|
+
if (!fs17.existsSync(dir)) return "artifacts directory does not exist yet";
|
|
5465
6182
|
return getPlatform().openPath(dir);
|
|
5466
6183
|
},
|
|
5467
6184
|
"task:getSpecs": async (taskId) => {
|
|
5468
6185
|
const task = requireTask(taskId);
|
|
5469
6186
|
const base = task.baseBranch ?? "main";
|
|
5470
6187
|
if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
|
|
5471
|
-
|
|
6188
|
+
const since = task.provisionedAt ?? task.launchedAt ?? (task.pushed !== void 0 ? task.createdAt : void 0);
|
|
6189
|
+
if (!task.projectPath || !since) return [];
|
|
5472
6190
|
const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
|
|
5473
|
-
return readBranchSpecs(task.projectPath, task.branch, base, changed,
|
|
6191
|
+
return readBranchSpecs(task.projectPath, task.branch, base, changed, since);
|
|
5474
6192
|
},
|
|
5475
6193
|
"board:getCliLaunchInfo": () => boardCliLaunchInfo(),
|
|
5476
6194
|
"library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
|
|
@@ -5570,24 +6288,31 @@ function createCore({ sessions, bus, version }) {
|
|
|
5570
6288
|
if (task.worktreePath) {
|
|
5571
6289
|
outcome = await captureTaskOutcome(task.worktreePath, task.baseBranch ?? "main") ?? void 0;
|
|
5572
6290
|
}
|
|
6291
|
+
const usage = usageThroughCurrentRun(task);
|
|
6292
|
+
progress.untrack(task.id);
|
|
5573
6293
|
if (task.projectPath && task.worktreePath) {
|
|
5574
6294
|
await removeTaskWorktree(task);
|
|
5575
6295
|
await syncBaseBranch(task.projectPath, task.baseBranch ?? "main");
|
|
5576
6296
|
}
|
|
5577
|
-
updateTask(task.id,
|
|
6297
|
+
updateTask(task.id, {
|
|
6298
|
+
worktreePath: void 0,
|
|
6299
|
+
...outcome ? { outcome } : {},
|
|
6300
|
+
...usage ? { usage } : {}
|
|
6301
|
+
});
|
|
5578
6302
|
return getState();
|
|
5579
6303
|
},
|
|
5580
6304
|
// Delete: cleanup (PTY + worktree) AND drop the card from the board.
|
|
5581
6305
|
"vibeflow:deleteTask": async (taskId) => {
|
|
5582
6306
|
const task = requireTask(taskId);
|
|
5583
6307
|
await teardownTask(task.id);
|
|
6308
|
+
progress.untrack(task.id);
|
|
5584
6309
|
cancelChatSend(task.id);
|
|
5585
6310
|
if (task.workspacePath) {
|
|
5586
6311
|
deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5587
6312
|
}
|
|
5588
6313
|
await removeTaskWorktree(task);
|
|
5589
6314
|
if (task.workspacePath) {
|
|
5590
|
-
|
|
6315
|
+
fs17.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
|
|
5591
6316
|
}
|
|
5592
6317
|
clearConversation(task.id);
|
|
5593
6318
|
removeTask(task.id);
|
|
@@ -5640,9 +6365,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5640
6365
|
// Watch the containing directory, not the file: JsonStore renames a temp
|
|
5641
6366
|
// file over the target and so replaces the inode on every save.
|
|
5642
6367
|
watchStore() {
|
|
5643
|
-
const file =
|
|
6368
|
+
const file = path21.basename(getStorePath());
|
|
5644
6369
|
let debounce = null;
|
|
5645
|
-
storeWatcher =
|
|
6370
|
+
storeWatcher = fs17.watch(path21.dirname(getStorePath()), (_event, filename) => {
|
|
5646
6371
|
if (filename !== file) return;
|
|
5647
6372
|
if (debounce) clearTimeout(debounce);
|
|
5648
6373
|
debounce = setTimeout(() => {
|
|
@@ -5658,6 +6383,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5658
6383
|
sessions.shutdown();
|
|
5659
6384
|
cancelAllChatSends();
|
|
5660
6385
|
unwatchAllSubAgents();
|
|
6386
|
+
progress.untrackAll();
|
|
5661
6387
|
storeWatcher?.close();
|
|
5662
6388
|
}
|
|
5663
6389
|
};
|
|
@@ -5674,9 +6400,9 @@ function createSessionBackend(sink, choice) {
|
|
|
5674
6400
|
}
|
|
5675
6401
|
|
|
5676
6402
|
// packages/core/src/web-server.ts
|
|
5677
|
-
import
|
|
6403
|
+
import fs18 from "fs";
|
|
5678
6404
|
import http from "http";
|
|
5679
|
-
import
|
|
6405
|
+
import path22 from "path";
|
|
5680
6406
|
import { timingSafeEqual } from "crypto";
|
|
5681
6407
|
import { WebSocketServer } from "ws";
|
|
5682
6408
|
var TOKEN_COOKIE = "vf_token";
|
|
@@ -5729,14 +6455,14 @@ function resolveStatic(root, urlPath) {
|
|
|
5729
6455
|
return null;
|
|
5730
6456
|
}
|
|
5731
6457
|
if (decoded.includes("\0")) return null;
|
|
5732
|
-
const rel =
|
|
5733
|
-
const base =
|
|
5734
|
-
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`];
|
|
5735
6461
|
for (const c of candidates) {
|
|
5736
|
-
const full =
|
|
5737
|
-
if (full !== base && !full.startsWith(base +
|
|
6462
|
+
const full = path22.resolve(base, c);
|
|
6463
|
+
if (full !== base && !full.startsWith(base + path22.sep)) return null;
|
|
5738
6464
|
try {
|
|
5739
|
-
if (
|
|
6465
|
+
if (fs18.statSync(full).isFile()) return full;
|
|
5740
6466
|
} catch {
|
|
5741
6467
|
}
|
|
5742
6468
|
}
|
|
@@ -5797,7 +6523,7 @@ async function startWebServer(options) {
|
|
|
5797
6523
|
send(res, 404, "Not found");
|
|
5798
6524
|
return;
|
|
5799
6525
|
}
|
|
5800
|
-
const ext =
|
|
6526
|
+
const ext = path22.extname(file).toLowerCase();
|
|
5801
6527
|
const immutable = url.pathname.startsWith("/_next/static/");
|
|
5802
6528
|
res.writeHead(200, {
|
|
5803
6529
|
"Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
|
|
@@ -5810,7 +6536,7 @@ async function startWebServer(options) {
|
|
|
5810
6536
|
res.end();
|
|
5811
6537
|
return;
|
|
5812
6538
|
}
|
|
5813
|
-
|
|
6539
|
+
fs18.createReadStream(file).pipe(res);
|
|
5814
6540
|
}
|
|
5815
6541
|
const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
|
|
5816
6542
|
function reject(socket, status, reason) {
|
|
@@ -5982,7 +6708,7 @@ function loginUrl(lock, pathname = "/home/") {
|
|
|
5982
6708
|
return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
|
|
5983
6709
|
}
|
|
5984
6710
|
function isWebDir(dir) {
|
|
5985
|
-
return
|
|
6711
|
+
return fs19.existsSync(path23.join(dir, "home", "index.html"));
|
|
5986
6712
|
}
|
|
5987
6713
|
|
|
5988
6714
|
// packages/cli/src/remote.ts
|
|
@@ -6099,8 +6825,8 @@ function connectToHost(lock) {
|
|
|
6099
6825
|
}
|
|
6100
6826
|
|
|
6101
6827
|
// packages/cli/src/doctor.ts
|
|
6102
|
-
import
|
|
6103
|
-
import
|
|
6828
|
+
import fs20 from "fs";
|
|
6829
|
+
import path24 from "path";
|
|
6104
6830
|
import { execFile as execFile8 } from "child_process";
|
|
6105
6831
|
import { promisify as promisify5 } from "util";
|
|
6106
6832
|
init_env();
|
|
@@ -6240,10 +6966,10 @@ async function runDoctor(options) {
|
|
|
6240
6966
|
});
|
|
6241
6967
|
}
|
|
6242
6968
|
try {
|
|
6243
|
-
|
|
6244
|
-
const probe =
|
|
6245
|
-
|
|
6246
|
-
|
|
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);
|
|
6247
6973
|
checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
|
|
6248
6974
|
} catch (err) {
|
|
6249
6975
|
checks.push({
|
|
@@ -6297,24 +7023,24 @@ Options:
|
|
|
6297
7023
|
`.trim();
|
|
6298
7024
|
function detectLayout() {
|
|
6299
7025
|
const file = fileURLToPath(import.meta.url);
|
|
6300
|
-
const here =
|
|
6301
|
-
const bundledWeb =
|
|
6302
|
-
if (isWebDir(bundledWeb) ||
|
|
7026
|
+
const here = path27.dirname(file);
|
|
7027
|
+
const bundledWeb = path27.join(here, "web");
|
|
7028
|
+
if (isWebDir(bundledWeb) || path27.basename(here) === "dist") {
|
|
6303
7029
|
return {
|
|
6304
|
-
version: readVersion(
|
|
7030
|
+
version: readVersion(path27.join(here, "..", "package.json")),
|
|
6305
7031
|
webDir: isWebDir(bundledWeb) ? bundledWeb : null,
|
|
6306
7032
|
sourceRoot: null,
|
|
6307
7033
|
cliEntry: file
|
|
6308
7034
|
};
|
|
6309
7035
|
}
|
|
6310
|
-
const root =
|
|
7036
|
+
const root = path27.resolve(here, "..", "..", "..");
|
|
6311
7037
|
const override = process.env.VIBEFLOW_WEB_DIR;
|
|
6312
|
-
const webDir = override && isWebDir(override) ? override : isWebDir(
|
|
6313
|
-
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 };
|
|
6314
7040
|
}
|
|
6315
7041
|
function readVersion(pkgPath) {
|
|
6316
7042
|
try {
|
|
6317
|
-
return JSON.parse(
|
|
7043
|
+
return JSON.parse(fs22.readFileSync(pkgPath, "utf8")).version;
|
|
6318
7044
|
} catch {
|
|
6319
7045
|
return "0.0.0";
|
|
6320
7046
|
}
|
|
@@ -6520,8 +7246,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
|
|
|
6520
7246
|
console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
|
|
6521
7247
|
return 1;
|
|
6522
7248
|
}
|
|
6523
|
-
const dir =
|
|
6524
|
-
if (!
|
|
7249
|
+
const dir = path27.resolve(target);
|
|
7250
|
+
if (!fs22.existsSync(dir) || !fs22.statSync(dir).isDirectory()) {
|
|
6525
7251
|
console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
|
|
6526
7252
|
return 1;
|
|
6527
7253
|
}
|