@oh-my-pi/pi-coding-agent 6.8.4 → 6.9.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/CHANGELOG.md +21 -0
- package/package.json +5 -6
- package/src/core/python-executor.ts +6 -10
- package/src/core/sdk.ts +0 -2
- package/src/core/settings-manager.ts +0 -105
- package/src/core/streaming-output.ts +10 -19
- package/src/core/tools/index.ts +0 -4
- package/src/core/tools/python.ts +3 -3
- package/src/index.ts +0 -2
- package/src/modes/interactive/components/settings-defs.ts +0 -70
- package/src/modes/interactive/components/settings-selector.ts +0 -1
- package/src/modes/interactive/controllers/event-controller.ts +0 -11
- package/src/modes/interactive/controllers/selector-controller.ts +0 -9
- package/src/modes/interactive/interactive-mode.ts +0 -58
- package/src/modes/interactive/types.ts +0 -15
- package/src/core/custom-commands/bundled/wt/index.ts +0 -435
- package/src/core/tools/git.ts +0 -213
- package/src/core/voice-controller.ts +0 -135
- package/src/core/voice-supervisor.ts +0 -976
- package/src/core/voice.ts +0 -314
- package/src/lib/worktree/collapse.ts +0 -180
- package/src/lib/worktree/constants.ts +0 -14
- package/src/lib/worktree/errors.ts +0 -23
- package/src/lib/worktree/git.ts +0 -60
- package/src/lib/worktree/index.ts +0 -15
- package/src/lib/worktree/operations.ts +0 -216
- package/src/lib/worktree/session.ts +0 -114
- package/src/lib/worktree/stats.ts +0 -67
- package/src/modes/interactive/utils/voice-manager.ts +0 -96
- package/src/prompts/tools/git.md +0 -9
- package/src/prompts/voice-summary.md +0 -12
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { WORKTREE_BASE } from "./constants";
|
|
4
|
-
import { WorktreeError, WorktreeErrorCode } from "./errors";
|
|
5
|
-
import { getRepoName, getRepoRoot, git } from "./git";
|
|
6
|
-
|
|
7
|
-
export interface Worktree {
|
|
8
|
-
path: string;
|
|
9
|
-
branch: string | null;
|
|
10
|
-
head: string;
|
|
11
|
-
isMain: boolean;
|
|
12
|
-
isDetached: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type WorktreePartial = Partial<Worktree> & { isDetached?: boolean };
|
|
16
|
-
|
|
17
|
-
function finalizeWorktree(entry: WorktreePartial, repoRoot: string): Worktree {
|
|
18
|
-
const wtPath = entry.path?.trim();
|
|
19
|
-
if (!wtPath) {
|
|
20
|
-
throw new Error("Invalid worktree entry");
|
|
21
|
-
}
|
|
22
|
-
const branch = entry.isDetached ? null : (entry.branch ?? null);
|
|
23
|
-
const isDetached = entry.isDetached ?? branch === null;
|
|
24
|
-
return {
|
|
25
|
-
path: wtPath,
|
|
26
|
-
branch,
|
|
27
|
-
head: entry.head ?? "",
|
|
28
|
-
isMain: path.resolve(wtPath) === path.resolve(repoRoot),
|
|
29
|
-
isDetached,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function parseWorktreeList(output: string, repoRoot: string): Worktree[] {
|
|
34
|
-
const worktrees: Worktree[] = [];
|
|
35
|
-
let current: WorktreePartial = {};
|
|
36
|
-
|
|
37
|
-
for (const line of output.split("\n")) {
|
|
38
|
-
if (line.startsWith("worktree ")) {
|
|
39
|
-
if (current.path) {
|
|
40
|
-
worktrees.push(finalizeWorktree(current, repoRoot));
|
|
41
|
-
}
|
|
42
|
-
current = { path: line.slice(9) };
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (line.startsWith("HEAD ")) {
|
|
47
|
-
current.head = line.slice(5);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (line.startsWith("branch ")) {
|
|
52
|
-
const raw = line.slice(7);
|
|
53
|
-
current.branch = raw.startsWith("refs/heads/") ? raw.slice("refs/heads/".length) : raw;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (line === "detached") {
|
|
58
|
-
current.isDetached = true;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (current.path) {
|
|
63
|
-
worktrees.push(finalizeWorktree(current, repoRoot));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return worktrees;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create a new worktree.
|
|
71
|
-
*/
|
|
72
|
-
export async function create(branch: string, options?: { base?: string; path?: string }): Promise<Worktree> {
|
|
73
|
-
const repoRoot = await getRepoRoot();
|
|
74
|
-
const repoName = await getRepoName();
|
|
75
|
-
const targetPath = options?.path ?? path.join(WORKTREE_BASE, repoName, branch);
|
|
76
|
-
const resolvedTarget = path.resolve(targetPath);
|
|
77
|
-
|
|
78
|
-
const existing = await list();
|
|
79
|
-
const conflict = existing.find((wt) => wt.branch === branch || path.resolve(wt.path) === resolvedTarget);
|
|
80
|
-
if (conflict) {
|
|
81
|
-
throw new WorktreeError(`Worktree already exists: ${conflict.path}`, WorktreeErrorCode.WORKTREE_EXISTS);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await mkdir(path.dirname(resolvedTarget), { recursive: true });
|
|
85
|
-
|
|
86
|
-
const branchExists = (await git(["rev-parse", "--verify", `refs/heads/${branch}`], repoRoot)).code === 0;
|
|
87
|
-
|
|
88
|
-
const args = branchExists
|
|
89
|
-
? ["worktree", "add", resolvedTarget, branch]
|
|
90
|
-
: ["worktree", "add", "-b", branch, resolvedTarget, options?.base ?? "HEAD"];
|
|
91
|
-
|
|
92
|
-
const result = await git(args, repoRoot);
|
|
93
|
-
if (result.code !== 0) {
|
|
94
|
-
const stderr = result.stderr.trim();
|
|
95
|
-
if (stderr.includes("already exists") || stderr.includes("already checked out")) {
|
|
96
|
-
throw new WorktreeError(stderr || "Worktree already exists", WorktreeErrorCode.WORKTREE_EXISTS);
|
|
97
|
-
}
|
|
98
|
-
throw new Error(stderr || "Failed to create worktree");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const updated = await list();
|
|
102
|
-
const created = updated.find((wt) => path.resolve(wt.path) === resolvedTarget);
|
|
103
|
-
if (!created) {
|
|
104
|
-
throw new Error("Worktree created but not found in list");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return created;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* List all worktrees for current repository.
|
|
112
|
-
*/
|
|
113
|
-
export async function list(): Promise<Worktree[]> {
|
|
114
|
-
const repoRoot = await getRepoRoot();
|
|
115
|
-
const result = await git(["worktree", "list", "--porcelain"], repoRoot);
|
|
116
|
-
if (result.code !== 0) {
|
|
117
|
-
throw new Error(result.stderr.trim() || "Failed to list worktrees");
|
|
118
|
-
}
|
|
119
|
-
return parseWorktreeList(result.stdout, repoRoot);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Find a worktree by pattern.
|
|
124
|
-
*/
|
|
125
|
-
export async function find(pattern: string): Promise<Worktree> {
|
|
126
|
-
const worktrees = await list();
|
|
127
|
-
|
|
128
|
-
const exactBranch = worktrees.filter((wt) => wt.branch === pattern);
|
|
129
|
-
if (exactBranch.length === 1) return exactBranch[0];
|
|
130
|
-
if (exactBranch.length > 1) {
|
|
131
|
-
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const exactDir = worktrees.filter((wt) => path.basename(wt.path) === pattern);
|
|
135
|
-
if (exactDir.length === 1) return exactDir[0];
|
|
136
|
-
if (exactDir.length > 1) {
|
|
137
|
-
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const partialBranch = worktrees.filter((wt) => wt.branch?.includes(pattern));
|
|
141
|
-
if (partialBranch.length === 1) return partialBranch[0];
|
|
142
|
-
if (partialBranch.length > 1) {
|
|
143
|
-
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const partialPath = worktrees.filter((wt) => wt.path.includes(pattern));
|
|
147
|
-
if (partialPath.length === 1) return partialPath[0];
|
|
148
|
-
if (partialPath.length > 1) {
|
|
149
|
-
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
throw new WorktreeError(`Worktree not found: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Remove a worktree.
|
|
157
|
-
*/
|
|
158
|
-
export async function remove(nameOrPath: string, options?: { force?: boolean }): Promise<void> {
|
|
159
|
-
const wt = await find(nameOrPath);
|
|
160
|
-
if (wt.isMain) {
|
|
161
|
-
throw new WorktreeError("Cannot remove main worktree", WorktreeErrorCode.CANNOT_MODIFY_MAIN);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const repoRoot = await getRepoRoot();
|
|
165
|
-
const args = ["worktree", "remove", wt.path];
|
|
166
|
-
if (options?.force) args.push("--force");
|
|
167
|
-
|
|
168
|
-
const result = await git(args, repoRoot);
|
|
169
|
-
if (result.code !== 0) {
|
|
170
|
-
throw new Error(result.stderr.trim() || "Failed to remove worktree");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Remove worktrees for branches that no longer exist.
|
|
176
|
-
*/
|
|
177
|
-
export async function prune(): Promise<number> {
|
|
178
|
-
const repoRoot = await getRepoRoot();
|
|
179
|
-
const worktrees = await list();
|
|
180
|
-
let removed = 0;
|
|
181
|
-
|
|
182
|
-
for (const wt of worktrees) {
|
|
183
|
-
if (wt.isMain || !wt.branch) continue;
|
|
184
|
-
const existsResult = await git(["rev-parse", "--verify", `refs/heads/${wt.branch}`], repoRoot);
|
|
185
|
-
if (existsResult.code === 0) continue;
|
|
186
|
-
|
|
187
|
-
const result = await git(["worktree", "remove", wt.path], repoRoot);
|
|
188
|
-
if (result.code !== 0) {
|
|
189
|
-
throw new Error(result.stderr.trim() || `Failed to remove worktree: ${wt.path}`);
|
|
190
|
-
}
|
|
191
|
-
removed += 1;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return removed;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get the worktree containing the given path.
|
|
199
|
-
* Returns null if path is not in any worktree.
|
|
200
|
-
*/
|
|
201
|
-
export async function which(targetPath?: string): Promise<Worktree | null> {
|
|
202
|
-
const worktrees = await list();
|
|
203
|
-
const resolved = path.resolve(targetPath ?? process.cwd());
|
|
204
|
-
|
|
205
|
-
let best: Worktree | null = null;
|
|
206
|
-
for (const wt of worktrees) {
|
|
207
|
-
const wtPath = path.resolve(wt.path);
|
|
208
|
-
if (resolved === wtPath || resolved.startsWith(wtPath + path.sep)) {
|
|
209
|
-
if (!best || wtPath.length > best.path.length) {
|
|
210
|
-
best = wt;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return best;
|
|
216
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { nanoid } from "nanoid";
|
|
4
|
-
import { getRepoRoot, git } from "./git";
|
|
5
|
-
|
|
6
|
-
export interface WorktreeSession {
|
|
7
|
-
id: string;
|
|
8
|
-
branch: string;
|
|
9
|
-
path: string;
|
|
10
|
-
scope?: string[];
|
|
11
|
-
agentId?: string;
|
|
12
|
-
task?: string;
|
|
13
|
-
status: SessionStatus;
|
|
14
|
-
createdAt: number;
|
|
15
|
-
completedAt?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type SessionStatus = "creating" | "active" | "completed" | "merging" | "merged" | "failed" | "abandoned";
|
|
19
|
-
|
|
20
|
-
async function getSessionsFile(): Promise<string> {
|
|
21
|
-
const repoRoot = await getRepoRoot();
|
|
22
|
-
const result = await git(["rev-parse", "--git-common-dir"], repoRoot);
|
|
23
|
-
let gitDir = result.code === 0 ? result.stdout.trim() : "";
|
|
24
|
-
if (!gitDir) {
|
|
25
|
-
gitDir = path.join(repoRoot, ".git");
|
|
26
|
-
}
|
|
27
|
-
if (!path.isAbsolute(gitDir)) {
|
|
28
|
-
// Resolve relative git dir from repo root to keep sessions in the common dir.
|
|
29
|
-
gitDir = path.resolve(repoRoot, gitDir);
|
|
30
|
-
}
|
|
31
|
-
await mkdir(gitDir, { recursive: true });
|
|
32
|
-
return path.join(gitDir, "worktree-sessions.json");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function loadSessions(): Promise<WorktreeSession[]> {
|
|
36
|
-
const filePath = await getSessionsFile();
|
|
37
|
-
const file = Bun.file(filePath);
|
|
38
|
-
if (!(await file.exists())) return [];
|
|
39
|
-
try {
|
|
40
|
-
const data = await file.json();
|
|
41
|
-
if (Array.isArray(data)) {
|
|
42
|
-
return data as WorktreeSession[];
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function saveSessions(sessions: WorktreeSession[]): Promise<void> {
|
|
51
|
-
const filePath = await getSessionsFile();
|
|
52
|
-
await Bun.write(filePath, JSON.stringify(sessions, null, 2));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function createSession(params: {
|
|
56
|
-
branch: string;
|
|
57
|
-
path: string;
|
|
58
|
-
scope?: string[];
|
|
59
|
-
task?: string;
|
|
60
|
-
}): Promise<WorktreeSession> {
|
|
61
|
-
const sessions = await loadSessions();
|
|
62
|
-
const session: WorktreeSession = {
|
|
63
|
-
id: nanoid(10),
|
|
64
|
-
branch: params.branch,
|
|
65
|
-
path: params.path,
|
|
66
|
-
scope: params.scope,
|
|
67
|
-
task: params.task,
|
|
68
|
-
status: "creating",
|
|
69
|
-
createdAt: Date.now(),
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
sessions.push(session);
|
|
73
|
-
await saveSessions(sessions);
|
|
74
|
-
return session;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function updateSession(id: string, updates: Partial<WorktreeSession>): Promise<void> {
|
|
78
|
-
const sessions = await loadSessions();
|
|
79
|
-
const idx = sessions.findIndex((s) => s.id === id);
|
|
80
|
-
if (idx === -1) return;
|
|
81
|
-
const current = sessions[idx];
|
|
82
|
-
sessions[idx] = { ...current, ...updates, id: current.id };
|
|
83
|
-
await saveSessions(sessions);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function getSession(id: string): Promise<WorktreeSession | null> {
|
|
87
|
-
const sessions = await loadSessions();
|
|
88
|
-
return sessions.find((s) => s.id === id) ?? null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function listSessions(): Promise<WorktreeSession[]> {
|
|
92
|
-
return loadSessions();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function cleanupSessions(): Promise<number> {
|
|
96
|
-
const sessions = await loadSessions();
|
|
97
|
-
let removed = 0;
|
|
98
|
-
|
|
99
|
-
const remaining: WorktreeSession[] = [];
|
|
100
|
-
for (const session of sessions) {
|
|
101
|
-
const exists = await Bun.file(session.path).exists();
|
|
102
|
-
if (!exists) {
|
|
103
|
-
removed += 1;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
remaining.push(session);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (removed > 0) {
|
|
110
|
-
await saveSessions(remaining);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return removed;
|
|
114
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { git } from "./git";
|
|
2
|
-
|
|
3
|
-
export interface WorktreeStats {
|
|
4
|
-
additions: number;
|
|
5
|
-
deletions: number;
|
|
6
|
-
untracked: number;
|
|
7
|
-
modified: number;
|
|
8
|
-
staged: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Get diff statistics for a worktree.
|
|
13
|
-
*/
|
|
14
|
-
export async function getStats(worktreePath: string): Promise<WorktreeStats> {
|
|
15
|
-
const diffResult = await git(["diff", "HEAD", "--shortstat"], worktreePath);
|
|
16
|
-
|
|
17
|
-
let additions = 0;
|
|
18
|
-
let deletions = 0;
|
|
19
|
-
|
|
20
|
-
const statsLine = diffResult.stdout.trim();
|
|
21
|
-
if (statsLine) {
|
|
22
|
-
const insertMatch = statsLine.match(/(\d+) insertion/);
|
|
23
|
-
const deleteMatch = statsLine.match(/(\d+) deletion/);
|
|
24
|
-
if (insertMatch) additions = parseInt(insertMatch[1], 10);
|
|
25
|
-
if (deleteMatch) deletions = parseInt(deleteMatch[1], 10);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const untrackedResult = await git(["ls-files", "--others", "--exclude-standard"], worktreePath);
|
|
29
|
-
const untracked = untrackedResult.stdout.trim() ? untrackedResult.stdout.trim().split("\n").length : 0;
|
|
30
|
-
|
|
31
|
-
const statusResult = await git(["status", "--porcelain"], worktreePath);
|
|
32
|
-
let modified = 0;
|
|
33
|
-
let staged = 0;
|
|
34
|
-
|
|
35
|
-
for (const line of statusResult.stdout.split("\n")) {
|
|
36
|
-
if (!line) continue;
|
|
37
|
-
const index = line[0];
|
|
38
|
-
const worktree = line[1];
|
|
39
|
-
if (index !== " " && index !== "?") staged += 1;
|
|
40
|
-
if (worktree !== " " && worktree !== "?") modified += 1;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { additions, deletions, untracked, modified, staged };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Format stats for display.
|
|
48
|
-
* Returns "clean" or "+N -M ?U" format.
|
|
49
|
-
*/
|
|
50
|
-
export function formatStats(stats: WorktreeStats): string {
|
|
51
|
-
if (
|
|
52
|
-
stats.additions === 0 &&
|
|
53
|
-
stats.deletions === 0 &&
|
|
54
|
-
stats.untracked === 0 &&
|
|
55
|
-
stats.modified === 0 &&
|
|
56
|
-
stats.staged === 0
|
|
57
|
-
) {
|
|
58
|
-
return "clean";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const parts: string[] = [];
|
|
62
|
-
if (stats.additions > 0) parts.push(`+${stats.additions}`);
|
|
63
|
-
if (stats.deletions > 0) parts.push(`-${stats.deletions}`);
|
|
64
|
-
if (stats.untracked > 0) parts.push(`?${stats.untracked}`);
|
|
65
|
-
|
|
66
|
-
return parts.join(" ") || "clean";
|
|
67
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type { InteractiveModeContext } from "../types";
|
|
2
|
-
|
|
3
|
-
const VOICE_PROGRESS_DELAY_MS = 15000;
|
|
4
|
-
const VOICE_PROGRESS_MIN_CHARS = 160;
|
|
5
|
-
const VOICE_PROGRESS_DELTA_CHARS = 120;
|
|
6
|
-
|
|
7
|
-
export class VoiceManager {
|
|
8
|
-
constructor(private ctx: InteractiveModeContext) {}
|
|
9
|
-
|
|
10
|
-
setVoiceStatus(text: string | undefined): void {
|
|
11
|
-
this.ctx.statusLine.setHookStatus("voice", text);
|
|
12
|
-
this.ctx.ui.requestRender();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async handleVoiceInterrupt(reason?: string): Promise<void> {
|
|
16
|
-
const now = Date.now();
|
|
17
|
-
if (now - this.ctx.lastVoiceInterruptAt < 200) return;
|
|
18
|
-
this.ctx.lastVoiceInterruptAt = now;
|
|
19
|
-
if (this.ctx.session.isBashRunning) {
|
|
20
|
-
this.ctx.session.abortBash();
|
|
21
|
-
}
|
|
22
|
-
if (this.ctx.session.isStreaming) {
|
|
23
|
-
await this.ctx.session.abort();
|
|
24
|
-
}
|
|
25
|
-
if (reason) {
|
|
26
|
-
this.ctx.showStatus(reason);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
stopVoiceProgressTimer(): void {
|
|
31
|
-
if (this.ctx.voiceProgressTimer) {
|
|
32
|
-
clearTimeout(this.ctx.voiceProgressTimer);
|
|
33
|
-
this.ctx.voiceProgressTimer = undefined;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
startVoiceProgressTimer(): void {
|
|
38
|
-
this.stopVoiceProgressTimer();
|
|
39
|
-
if (!this.ctx.settingsManager.getVoiceEnabled() || !this.ctx.voiceAutoModeEnabled) return;
|
|
40
|
-
this.ctx.voiceProgressSpoken = false;
|
|
41
|
-
this.ctx.voiceProgressLastLength = 0;
|
|
42
|
-
this.ctx.voiceProgressTimer = setTimeout(() => {
|
|
43
|
-
void this.maybeSpeakProgress();
|
|
44
|
-
}, VOICE_PROGRESS_DELAY_MS);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async maybeSpeakProgress(): Promise<void> {
|
|
48
|
-
if (!this.ctx.session.isStreaming || this.ctx.voiceProgressSpoken || !this.ctx.voiceAutoModeEnabled) return;
|
|
49
|
-
const streaming = this.ctx.streamingMessage;
|
|
50
|
-
if (!streaming) return;
|
|
51
|
-
const text = this.ctx.extractAssistantText(streaming);
|
|
52
|
-
if (!text || text.length < VOICE_PROGRESS_MIN_CHARS) {
|
|
53
|
-
if (this.ctx.session.isStreaming) {
|
|
54
|
-
this.ctx.voiceProgressTimer = setTimeout(() => {
|
|
55
|
-
void this.maybeSpeakProgress();
|
|
56
|
-
}, VOICE_PROGRESS_DELAY_MS);
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const delta = text.length - this.ctx.voiceProgressLastLength;
|
|
62
|
-
if (delta < VOICE_PROGRESS_DELTA_CHARS) {
|
|
63
|
-
if (this.ctx.session.isStreaming) {
|
|
64
|
-
this.ctx.voiceProgressTimer = setTimeout(() => {
|
|
65
|
-
void this.maybeSpeakProgress();
|
|
66
|
-
}, VOICE_PROGRESS_DELAY_MS);
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.ctx.voiceProgressLastLength = text.length;
|
|
72
|
-
this.ctx.voiceProgressSpoken = true;
|
|
73
|
-
this.ctx.voiceSupervisor.notifyProgress(text);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async submitVoiceText(text: string): Promise<void> {
|
|
77
|
-
const cleaned = text.trim();
|
|
78
|
-
if (!cleaned) {
|
|
79
|
-
this.ctx.showWarning("No speech detected. Try again.");
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const toSend = cleaned;
|
|
83
|
-
this.ctx.editor.addToHistory(toSend);
|
|
84
|
-
|
|
85
|
-
if (this.ctx.session.isStreaming) {
|
|
86
|
-
await this.ctx.session.abort();
|
|
87
|
-
await this.ctx.session.steer(toSend);
|
|
88
|
-
this.ctx.updatePendingMessagesDisplay();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (this.ctx.onInputCallback) {
|
|
93
|
-
this.ctx.onInputCallback({ text: toSend });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
package/src/prompts/tools/git.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Structured Git operations with safety guards and typed output. Use this tool instead of raw git commands.
|
|
2
|
-
|
|
3
|
-
Operations:
|
|
4
|
-
- READ: status, diff, log, show, blame, branch
|
|
5
|
-
- WRITE: add, restore, commit, checkout, merge, rebase, stash, cherry-pick
|
|
6
|
-
- REMOTE: fetch, pull, push, tag
|
|
7
|
-
- GITHUB: pr, issue, ci, release
|
|
8
|
-
|
|
9
|
-
Returns structured data plus a rendered summary for display. Safety checks may block or require confirmation for destructive actions.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
You are a voice summarizer for a coding agent.
|
|
2
|
-
|
|
3
|
-
Summarize the assistant response for spoken playback.
|
|
4
|
-
|
|
5
|
-
Rules:
|
|
6
|
-
- Output 1 to 3 sentences, maximum 50 words.
|
|
7
|
-
- No markdown, no code blocks, no inline code, no URLs.
|
|
8
|
-
- If the response already fits, return it unchanged.
|
|
9
|
-
- Preserve the most important question if one is asked.
|
|
10
|
-
- If a decision or missing info is required, ask one short question.
|
|
11
|
-
|
|
12
|
-
Return only the spoken text.
|