careervivid 2.1.21 → 2.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/QueryEngine.d.ts.map +1 -1
- package/dist/agent/QueryEngine.js +36 -4
- package/dist/agent/agentAuditLog.d.ts.map +1 -1
- package/dist/agent/agentAuditLog.js +45 -1
- package/dist/agent/instructions.d.ts +1 -0
- package/dist/agent/instructions.d.ts.map +1 -1
- package/dist/agent/instructions.js +96 -26
- package/dist/agent/memory.d.ts +28 -0
- package/dist/agent/memory.d.ts.map +1 -1
- package/dist/agent/memory.js +59 -0
- package/dist/agent/tools/coding.d.ts.map +1 -1
- package/dist/agent/tools/coding.js +27 -34
- package/dist/commands/agent/engineResolver.js +1 -1
- package/dist/commands/agent/index.d.ts.map +1 -1
- package/dist/commands/agent/index.js +11 -4
- package/dist/commands/agent/personas.d.ts +41 -0
- package/dist/commands/agent/personas.d.ts.map +1 -0
- package/dist/commands/agent/personas.js +323 -0
- package/dist/commands/agent/repl/engineLoop.d.ts +35 -0
- package/dist/commands/agent/repl/engineLoop.d.ts.map +1 -0
- package/dist/commands/agent/repl/engineLoop.js +168 -0
- package/dist/commands/agent/repl/input.d.ts +30 -0
- package/dist/commands/agent/repl/input.d.ts.map +1 -0
- package/dist/commands/agent/repl/input.js +86 -0
- package/dist/commands/agent/repl/slashCommands.d.ts +33 -0
- package/dist/commands/agent/repl/slashCommands.d.ts.map +1 -0
- package/dist/commands/agent/repl/slashCommands.js +193 -0
- package/dist/commands/agent/repl/toolHandlers.d.ts +33 -0
- package/dist/commands/agent/repl/toolHandlers.d.ts.map +1 -0
- package/dist/commands/agent/repl/toolHandlers.js +185 -0
- package/dist/commands/agent/repl.d.ts +12 -1
- package/dist/commands/agent/repl.d.ts.map +1 -1
- package/dist/commands/agent/repl.js +134 -636
- package/dist/commands/agent/toolRegistry.d.ts.map +1 -1
- package/dist/commands/agent/toolRegistry.js +4 -2
- package/dist/lib/tts.d.ts +19 -9
- package/dist/lib/tts.d.ts.map +1 -1
- package/dist/lib/tts.js +129 -50
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repl/slashCommands.ts
|
|
3
|
+
*
|
|
4
|
+
* Handles all slash (/) commands typed in the REPL:
|
|
5
|
+
* /help, /voice, /speak, /models, /model
|
|
6
|
+
*
|
|
7
|
+
* Returns `true` if the command was handled (caller should re-prompt),
|
|
8
|
+
* returns `false` if the input was not a slash command.
|
|
9
|
+
*/
|
|
10
|
+
interface ModelSwitchContext {
|
|
11
|
+
currentModel: string;
|
|
12
|
+
cvApiKey: string | undefined;
|
|
13
|
+
engine: any;
|
|
14
|
+
systemInstruction: string;
|
|
15
|
+
tools: any[];
|
|
16
|
+
options: {
|
|
17
|
+
think?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface SlashCommandResult {
|
|
21
|
+
/** If model was switched, contains the new values */
|
|
22
|
+
modelSwitch?: {
|
|
23
|
+
newModel: string;
|
|
24
|
+
newEngine: any;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Dispatch a slash command. Returns the result or null if not a slash command.
|
|
29
|
+
* Always returns non-null for slash inputs — caller should re-prompt after.
|
|
30
|
+
*/
|
|
31
|
+
export declare function handleSlashCommand(input: string, currentModel: string, modelCtx: Omit<ModelSwitchContext, "currentModel">): Promise<SlashCommandResult | null>;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=slashCommands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slashCommands.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/repl/slashCommands.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6IH,UAAU,kBAAkB;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,GAAG,CAAC;IACZ,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,GAAG,EAAE,CAAC;IACb,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B;AAyCD,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,GAAG,CAAA;KAAE,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,GACjD,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkCpC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repl/slashCommands.ts
|
|
3
|
+
*
|
|
4
|
+
* Handles all slash (/) commands typed in the REPL:
|
|
5
|
+
* /help, /voice, /speak, /models, /model
|
|
6
|
+
*
|
|
7
|
+
* Returns `true` if the command was handled (caller should re-prompt),
|
|
8
|
+
* returns `false` if the input was not a slash command.
|
|
9
|
+
*/
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import pkg from "enquirer";
|
|
12
|
+
import { CV_MODELS } from "../configurator.js";
|
|
13
|
+
import { CareerVividProxyEngine } from "../../../agent/CareerVividProxyEngine.js";
|
|
14
|
+
import { isVoiceEnabled, setVoiceEnabled, stopPlayback, getLastResponse, speakText, getCurrentVoice, setCurrentVoice, getCurrentTtsModel, setCurrentTtsModel, AVAILABLE_VOICES, AVAILABLE_TTS_MODELS, } from "../../../lib/tts.js";
|
|
15
|
+
const { prompt } = pkg;
|
|
16
|
+
// ── /help ─────────────────────────────────────────────────────────────────────
|
|
17
|
+
function handleHelp() {
|
|
18
|
+
console.log(chalk.cyan("\n Slash commands:"));
|
|
19
|
+
console.log(chalk.dim(" /model <name> — Switch to a different model mid-session"));
|
|
20
|
+
console.log(chalk.dim(" /models — List all available CareerVivid models"));
|
|
21
|
+
console.log(chalk.dim(" /voice — Voice / TTS settings (interactive)"));
|
|
22
|
+
console.log(chalk.dim(" /voice on|off — Quick toggle"));
|
|
23
|
+
console.log(chalk.dim(" /speak — Read the last agent response aloud"));
|
|
24
|
+
console.log(chalk.dim(" /help — Show this help message"));
|
|
25
|
+
console.log(chalk.dim(" exit — End the session"));
|
|
26
|
+
console.log(chalk.cyan("\n Shell escape (run terminal commands without leaving the agent):"));
|
|
27
|
+
console.log(chalk.dim(" !<command> — e.g. !ls -la or !git status\n"));
|
|
28
|
+
console.log(chalk.cyan(" Paste long content (job descriptions, cover letters):"));
|
|
29
|
+
console.log(chalk.dim(" <<< — Open multi-line paste mode; press Enter twice when done"));
|
|
30
|
+
console.log(chalk.dim(" <<<your text — Start with text directly after <<<\n"));
|
|
31
|
+
}
|
|
32
|
+
// ── /voice (interactive select) ───────────────────────────────────────────────
|
|
33
|
+
async function handleVoice(arg) {
|
|
34
|
+
// Quick text shortcuts (scriptable / muscle-memory)
|
|
35
|
+
if (arg === "on") {
|
|
36
|
+
setVoiceEnabled(true);
|
|
37
|
+
console.log(chalk.green(`\n 🔊 Voice on (${getCurrentVoice()} · ${getCurrentTtsModel()})\n`));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (arg === "off") {
|
|
41
|
+
setVoiceEnabled(false);
|
|
42
|
+
stopPlayback();
|
|
43
|
+
console.log(chalk.yellow("\n 🔇 Voice off\n"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Interactive top-level menu
|
|
47
|
+
const topChoice = await prompt({
|
|
48
|
+
type: "select",
|
|
49
|
+
name: "action",
|
|
50
|
+
message: `Voice settings (${chalk.dim(`voice: ${getCurrentVoice()} model: ${getCurrentTtsModel()}}`)})`,
|
|
51
|
+
choices: [
|
|
52
|
+
{ name: "toggle", message: isVoiceEnabled() ? "🔇 Turn voice off" : "🔊 Turn voice on" },
|
|
53
|
+
{ name: "set-voice", message: "🎵 Pick a voice" },
|
|
54
|
+
{ name: "set-model", message: "⚙️ Pick a TTS model" },
|
|
55
|
+
{ name: "speak", message: "▶️ Replay last response" },
|
|
56
|
+
{ name: "cancel", message: chalk.dim("Cancel") },
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
if (topChoice.action === "toggle") {
|
|
60
|
+
const newState = !isVoiceEnabled();
|
|
61
|
+
setVoiceEnabled(newState);
|
|
62
|
+
if (!newState)
|
|
63
|
+
stopPlayback();
|
|
64
|
+
console.log(newState
|
|
65
|
+
? chalk.green(`\n 🔊 Voice on (${getCurrentVoice()} · ${getCurrentTtsModel()})\n`)
|
|
66
|
+
: chalk.yellow("\n 🔇 Voice off\n"));
|
|
67
|
+
}
|
|
68
|
+
else if (topChoice.action === "set-voice") {
|
|
69
|
+
const { voice } = await prompt({
|
|
70
|
+
type: "select",
|
|
71
|
+
name: "voice",
|
|
72
|
+
message: "Choose a voice:",
|
|
73
|
+
choices: AVAILABLE_VOICES.map((v) => ({
|
|
74
|
+
name: v,
|
|
75
|
+
message: v === getCurrentVoice() ? chalk.green(`${v} ← active`) : v,
|
|
76
|
+
})),
|
|
77
|
+
});
|
|
78
|
+
setCurrentVoice(voice);
|
|
79
|
+
console.log(chalk.green(`\n 🎵 Voice set to ${chalk.bold(voice)}\n`));
|
|
80
|
+
}
|
|
81
|
+
else if (topChoice.action === "set-model") {
|
|
82
|
+
const MODEL_LABELS = {
|
|
83
|
+
"gemini-3.1-flash-tts-preview": "Gemini 3.1 Flash (latest, fast)",
|
|
84
|
+
"gemini-2.5-flash-preview-tts": "Gemini 2.5 Flash (previous gen, fast)",
|
|
85
|
+
"gemini-2.5-pro-preview-tts": "Gemini 2.5 Pro (previous gen, high quality)",
|
|
86
|
+
};
|
|
87
|
+
const { model } = await prompt({
|
|
88
|
+
type: "select",
|
|
89
|
+
name: "model",
|
|
90
|
+
message: "Choose a TTS model:",
|
|
91
|
+
choices: AVAILABLE_TTS_MODELS.map((m) => ({
|
|
92
|
+
name: m,
|
|
93
|
+
message: m === getCurrentTtsModel()
|
|
94
|
+
? chalk.green(`${MODEL_LABELS[m] ?? m} ← active`)
|
|
95
|
+
: (MODEL_LABELS[m] ?? m),
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
setCurrentTtsModel(model);
|
|
99
|
+
console.log(chalk.green(`\n ⚙️ TTS model set to ${chalk.bold(model)}\n`));
|
|
100
|
+
}
|
|
101
|
+
else if (topChoice.action === "speak") {
|
|
102
|
+
const last = getLastResponse();
|
|
103
|
+
if (!last) {
|
|
104
|
+
console.log(chalk.dim("\n Nothing to speak yet.\n"));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
speakText(last).catch(() => { });
|
|
108
|
+
console.log(chalk.dim("\n 🔊 Speaking...\n"));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ── /speak ────────────────────────────────────────────────────────────────────
|
|
113
|
+
function handleSpeak() {
|
|
114
|
+
const last = getLastResponse();
|
|
115
|
+
if (!last) {
|
|
116
|
+
console.log(chalk.dim("\n Nothing to speak yet. Ask the agent something first.\n"));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
speakText(last).catch(() => { });
|
|
120
|
+
console.log(chalk.dim("\n 🔊 Speaking last response...\n"));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ── /models ───────────────────────────────────────────────────────────────────
|
|
124
|
+
function handleModels(currentModel) {
|
|
125
|
+
console.log(chalk.cyan("\n Available CareerVivid models:"));
|
|
126
|
+
for (const m of CV_MODELS) {
|
|
127
|
+
const active = m.value === currentModel ? chalk.green(" ← active") : "";
|
|
128
|
+
console.log(` ${m.name}${active}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(chalk.dim("\n Usage: /model gemini-2.5-flash\n"));
|
|
131
|
+
}
|
|
132
|
+
function handleModel(arg, ctx) {
|
|
133
|
+
if (!arg) {
|
|
134
|
+
console.log(chalk.yellow(`\n Current model: ${chalk.bold(ctx.currentModel)}`));
|
|
135
|
+
console.log(chalk.dim(" Usage: /model <name> e.g. /model gemini-3.1-pro-preview"));
|
|
136
|
+
console.log(chalk.dim(" Run /models to see all available options.\n"));
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const known = CV_MODELS.find(m => m.value === arg);
|
|
140
|
+
if (!known && !arg.includes("/") && !arg.includes("-")) {
|
|
141
|
+
console.log(chalk.red(`\n Unknown model: ${arg}`));
|
|
142
|
+
console.log(chalk.dim(" Run /models to see available options.\n"));
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
let newEngine = ctx.engine;
|
|
146
|
+
if (ctx.cvApiKey && ctx.engine instanceof CareerVividProxyEngine) {
|
|
147
|
+
newEngine = new CareerVividProxyEngine({
|
|
148
|
+
cvApiKey: ctx.cvApiKey,
|
|
149
|
+
model: arg,
|
|
150
|
+
systemInstruction: ctx.systemInstruction,
|
|
151
|
+
tools: ctx.tools,
|
|
152
|
+
thinkingBudget: arg.includes("pro") ? (ctx.options.think ?? 8192) : 0,
|
|
153
|
+
maxHistoryLength: 40,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const creditInfo = known ? chalk.dim(` (${known.cost} credit/turn)`) : "";
|
|
157
|
+
console.log(chalk.green(`\n ✔ Switched to ${chalk.bold(arg)}${creditInfo}`));
|
|
158
|
+
console.log(chalk.dim(" Conversation history has been reset.\n"));
|
|
159
|
+
return { newModel: arg, newEngine };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Dispatch a slash command. Returns the result or null if not a slash command.
|
|
163
|
+
* Always returns non-null for slash inputs — caller should re-prompt after.
|
|
164
|
+
*/
|
|
165
|
+
export async function handleSlashCommand(input, currentModel, modelCtx) {
|
|
166
|
+
if (!input.startsWith("/"))
|
|
167
|
+
return null;
|
|
168
|
+
const [cmd, ...rest] = input.slice(1).split(" ");
|
|
169
|
+
const arg = rest.join(" ").trim();
|
|
170
|
+
switch (cmd) {
|
|
171
|
+
case "help":
|
|
172
|
+
handleHelp();
|
|
173
|
+
break;
|
|
174
|
+
case "voice":
|
|
175
|
+
await handleVoice(arg);
|
|
176
|
+
break;
|
|
177
|
+
case "speak":
|
|
178
|
+
handleSpeak();
|
|
179
|
+
break;
|
|
180
|
+
case "models":
|
|
181
|
+
handleModels(currentModel);
|
|
182
|
+
break;
|
|
183
|
+
case "model": {
|
|
184
|
+
const result = handleModel(arg, { ...modelCtx, currentModel });
|
|
185
|
+
if (result)
|
|
186
|
+
return { modelSwitch: result };
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
default:
|
|
190
|
+
console.log(chalk.yellow(`\n Unknown command: /${cmd}. Type /help for available commands.\n`));
|
|
191
|
+
}
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repl/toolHandlers.ts
|
|
3
|
+
*
|
|
4
|
+
* Tool call confirmation, spinner lifecycle, mutation budgets,
|
|
5
|
+
* circuit breaker, and audit logging for the REPL.
|
|
6
|
+
*/
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
export declare const WRITE_TOOLS: Set<string>;
|
|
9
|
+
export declare const SESSION_MAX_MUTATIONS = 25;
|
|
10
|
+
export declare const TURN_MAX_MUTATIONS = 10;
|
|
11
|
+
export interface ToolHandlerState {
|
|
12
|
+
sessionMutations: number;
|
|
13
|
+
turnMutations: number;
|
|
14
|
+
trustAllCommands: boolean;
|
|
15
|
+
trustAllWrites: boolean;
|
|
16
|
+
currentSpinner: ReturnType<typeof ora> | null;
|
|
17
|
+
lastToolCall: {
|
|
18
|
+
name: string;
|
|
19
|
+
argsHash: string;
|
|
20
|
+
count: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function createToolHandlerState(): ToolHandlerState;
|
|
24
|
+
/**
|
|
25
|
+
* Called before a tool executes. Returns true to allow, false to deny.
|
|
26
|
+
* Manages confirmation prompts, spinner start, and mutation budgets.
|
|
27
|
+
*/
|
|
28
|
+
export declare function onToolCall(name: string, args: any, thinkingSpinner: ReturnType<typeof ora>, state: ToolHandlerState): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* Called after a tool completes. Stops spinner, logs audit entry.
|
|
31
|
+
*/
|
|
32
|
+
export declare function onToolResult(name: string, result: any, state: ToolHandlerState): void;
|
|
33
|
+
//# sourceMappingURL=toolHandlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolHandlers.d.ts","sourceRoot":"","sources":["../../../../src/commands/agent/repl/toolHandlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,GAAG,MAAM,KAAK,CAAC;AAQtB,eAAO,MAAM,WAAW,aAItB,CAAC;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AA6CrC,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC;IAC9C,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CASzD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,eAAe,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,EACvC,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,OAAO,CAAC,CAmGlB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,gBAAgB,QAe9E"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repl/toolHandlers.ts
|
|
3
|
+
*
|
|
4
|
+
* Tool call confirmation, spinner lifecycle, mutation budgets,
|
|
5
|
+
* circuit breaker, and audit logging for the REPL.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import ora from "ora";
|
|
9
|
+
import pkg from "enquirer";
|
|
10
|
+
import { isSafeCommand } from "../../../agent/tools/coding.js";
|
|
11
|
+
import { auditLog, SESSION_ID } from "../../../agent/agentAuditLog.js";
|
|
12
|
+
const { prompt } = pkg;
|
|
13
|
+
// ── Mutation budget constants ──────────────────────────────────────────────────
|
|
14
|
+
export const WRITE_TOOLS = new Set([
|
|
15
|
+
"tracker_add_job", "tracker_update_job", "kanban_add_job", "kanban_update_status",
|
|
16
|
+
"save_cover_letter", "delete_cover_letter", "write_file", "patch_file",
|
|
17
|
+
"tracker_recheck_urls", "openings_apply",
|
|
18
|
+
]);
|
|
19
|
+
export const SESSION_MAX_MUTATIONS = 25;
|
|
20
|
+
export const TURN_MAX_MUTATIONS = 10;
|
|
21
|
+
// ── Tool label map ─────────────────────────────────────────────────────────────
|
|
22
|
+
const TOOL_LABELS = {
|
|
23
|
+
list_directory: "🔍 Scanning workspace...",
|
|
24
|
+
read_file: "📖 Reading file...",
|
|
25
|
+
run_command: "⚙️ Running command...",
|
|
26
|
+
write_file: "✏️ Writing file...",
|
|
27
|
+
patch_file: "✏️ Patching file...",
|
|
28
|
+
tracker_list_jobs: "📊 Checking job pipeline...",
|
|
29
|
+
tracker_add_job: "➕ Adding job to pipeline...",
|
|
30
|
+
tracker_update_job: "🔄 Updating job record...",
|
|
31
|
+
tracker_rank_priority: "📈 Ranking pipeline...",
|
|
32
|
+
tracker_dashboard: "📊 Fetching pipeline analytics...",
|
|
33
|
+
tracker_find_stale: "🚩 Checking stale jobs...",
|
|
34
|
+
tracker_inspect_quality: "🔍 Inspecting data quality...",
|
|
35
|
+
kanban_add_job: "📌 Saving to Kanban board...",
|
|
36
|
+
kanban_list_jobs: "📋 Loading Kanban board...",
|
|
37
|
+
kanban_update_status: "🔄 Updating Kanban status...",
|
|
38
|
+
list_cover_letters: "📄 Loading cover letters...",
|
|
39
|
+
get_cover_letter: "📄 Reading cover letter...",
|
|
40
|
+
save_cover_letter: "💾 Saving cover letter...",
|
|
41
|
+
delete_cover_letter: "🗑️ Deleting cover letter...",
|
|
42
|
+
browser_navigate: "🌐 Navigating to page...",
|
|
43
|
+
browser_click: "🖱️ Clicking element...",
|
|
44
|
+
browser_type: "⌨️ Typing input...",
|
|
45
|
+
browser_state: "🌐 Reading browser state...",
|
|
46
|
+
browser_screenshot: "📸 Taking screenshot...",
|
|
47
|
+
browser_scroll: "📜 Scrolling page...",
|
|
48
|
+
browser_wait: "⏳ Waiting...",
|
|
49
|
+
browser_close: "🔒 Closing browser...",
|
|
50
|
+
browser_select: "🖱️ Selecting option...",
|
|
51
|
+
tracker_recheck_urls: "🔗 Re-checking job URLs...",
|
|
52
|
+
browser_autofill_application: "📝 Auto-filling application...",
|
|
53
|
+
verify_url: "🔍 Verifying URL...",
|
|
54
|
+
verify_job_urls: "🔍 Verifying job URLs...",
|
|
55
|
+
search_jobs: "🔍 Searching jobs...",
|
|
56
|
+
openings_scan: "🎯 Scanning companies for open roles...",
|
|
57
|
+
openings_list: "📋 Loading saved openings...",
|
|
58
|
+
openings_apply: "✅ Marking opening as applied...",
|
|
59
|
+
get_resume: "📄 Loading resume...",
|
|
60
|
+
list_resumes: "📄 Loading resumes...",
|
|
61
|
+
get_profile: "👤 Loading profile...",
|
|
62
|
+
};
|
|
63
|
+
export function createToolHandlerState() {
|
|
64
|
+
return {
|
|
65
|
+
sessionMutations: 0,
|
|
66
|
+
turnMutations: 0,
|
|
67
|
+
trustAllCommands: false,
|
|
68
|
+
trustAllWrites: false,
|
|
69
|
+
currentSpinner: null,
|
|
70
|
+
lastToolCall: { name: "", argsHash: "", count: 0 },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Called before a tool executes. Returns true to allow, false to deny.
|
|
75
|
+
* Manages confirmation prompts, spinner start, and mutation budgets.
|
|
76
|
+
*/
|
|
77
|
+
export async function onToolCall(name, args, thinkingSpinner, state) {
|
|
78
|
+
// Stop thinking spinner on first tool call
|
|
79
|
+
if (thinkingSpinner.isSpinning) {
|
|
80
|
+
thinkingSpinner.stop();
|
|
81
|
+
process.stdout.write("\r\x1b[K");
|
|
82
|
+
}
|
|
83
|
+
// ── Circuit breaker ──────────────────────────────────────────────────────
|
|
84
|
+
const argsHash = JSON.stringify(args).slice(0, 100);
|
|
85
|
+
if (state.lastToolCall.name === name && state.lastToolCall.argsHash === argsHash) {
|
|
86
|
+
state.lastToolCall.count++;
|
|
87
|
+
if (state.lastToolCall.count >= 5) {
|
|
88
|
+
console.log(chalk.red(`\n⛔ Loop detected: "${name}" called ${state.lastToolCall.count} times with identical args. Aborting turn.`));
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
state.lastToolCall = { name, argsHash, count: 1 };
|
|
94
|
+
}
|
|
95
|
+
// ── Mutation budget ───────────────────────────────────────────────────────
|
|
96
|
+
if (WRITE_TOOLS.has(name)) {
|
|
97
|
+
state.turnMutations++;
|
|
98
|
+
if (state.turnMutations > TURN_MAX_MUTATIONS) {
|
|
99
|
+
console.log(chalk.red(`\n⛔ Turn mutation limit (${TURN_MAX_MUTATIONS}) reached. The agent has made ${state.turnMutations} writes this turn.`));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
state.sessionMutations++;
|
|
103
|
+
if (state.sessionMutations >= SESSION_MAX_MUTATIONS) {
|
|
104
|
+
console.log(chalk.yellow(`\n⚠️ Session mutation budget exhausted (${SESSION_MAX_MUTATIONS} writes). Restart the agent to continue writing.`));
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
else if (state.sessionMutations === SESSION_MAX_MUTATIONS - 5) {
|
|
108
|
+
console.log(chalk.yellow(`\n💡 Heads up: ${SESSION_MAX_MUTATIONS - state.sessionMutations} writes remaining this session.`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Print compact tool label
|
|
112
|
+
process.stdout.write(chalk.dim(` ${TOOL_LABELS[name] ?? "⚙️ Working..."}\n`));
|
|
113
|
+
// ── Per-tool confirmations ────────────────────────────────────────────────
|
|
114
|
+
if (name === "run_command") {
|
|
115
|
+
if (state.trustAllCommands || isSafeCommand(args.command))
|
|
116
|
+
return true;
|
|
117
|
+
const confirm = await prompt({
|
|
118
|
+
type: "select",
|
|
119
|
+
name: "ok",
|
|
120
|
+
message: `Allow running: ${chalk.bold(args.command)}?`,
|
|
121
|
+
choices: ["Yes, run it", "Yes, and trust all commands this session", "No, skip it"],
|
|
122
|
+
});
|
|
123
|
+
if (confirm.ok === "Yes, and trust all commands this session") {
|
|
124
|
+
state.trustAllCommands = true;
|
|
125
|
+
console.log(chalk.dim(" ✅ All commands will run automatically for the rest of this session."));
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return confirm.ok === "Yes, run it";
|
|
129
|
+
}
|
|
130
|
+
if (name === "write_file" || name === "patch_file") {
|
|
131
|
+
if (state.trustAllWrites)
|
|
132
|
+
return true;
|
|
133
|
+
const target = args.path || "(unknown path)";
|
|
134
|
+
const confirm = await prompt({
|
|
135
|
+
type: "select",
|
|
136
|
+
name: "ok",
|
|
137
|
+
message: `Allow writing to: ${chalk.bold(target)}?`,
|
|
138
|
+
choices: ["Yes, write it", "Yes, and trust all writes this session", "No, skip it"],
|
|
139
|
+
});
|
|
140
|
+
if (confirm.ok === "Yes, and trust all writes this session") {
|
|
141
|
+
state.trustAllWrites = true;
|
|
142
|
+
console.log(chalk.dim(" ✅ All file writes will run automatically for the rest of this session."));
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (confirm.ok !== "Yes, write it")
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (name === "browser_close") {
|
|
149
|
+
const confirm = await prompt({
|
|
150
|
+
type: "select",
|
|
151
|
+
name: "ok",
|
|
152
|
+
message: "Close the browser?",
|
|
153
|
+
choices: ["Yes, close it", "No, keep it open"],
|
|
154
|
+
});
|
|
155
|
+
if (confirm.ok !== "Yes, close it")
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
// Interview tool takes over the terminal — stop spinner, yield cleanly
|
|
159
|
+
if (name === "start_interview") {
|
|
160
|
+
process.stdout.write("\r\x1b[K");
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
// All other tools: start a generic spinner
|
|
164
|
+
state.currentSpinner = ora(`Running ${chalk.bold(name)}...`).start();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Called after a tool completes. Stops spinner, logs audit entry.
|
|
169
|
+
*/
|
|
170
|
+
export function onToolResult(name, result, state) {
|
|
171
|
+
if (state.currentSpinner) {
|
|
172
|
+
state.currentSpinner.succeed(chalk.dim("Done"));
|
|
173
|
+
state.currentSpinner = null;
|
|
174
|
+
}
|
|
175
|
+
if (name === "start_interview") {
|
|
176
|
+
console.log(chalk.dim("─".repeat(50)));
|
|
177
|
+
}
|
|
178
|
+
auditLog({
|
|
179
|
+
sessionId: SESSION_ID,
|
|
180
|
+
tool: name,
|
|
181
|
+
args: typeof result?._args === "object" ? result._args : {},
|
|
182
|
+
result: typeof result === "string" ? result : JSON.stringify(result ?? ""),
|
|
183
|
+
durationMs: 0,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repl.ts — REPL orchestrator for the CareerVivid agent
|
|
3
|
+
*
|
|
4
|
+
* This file is intentionally thin. Each concern lives in its own module:
|
|
5
|
+
*
|
|
6
|
+
* repl/input.ts — User input: first-turn menu, paste buffer, <<<
|
|
7
|
+
* repl/slashCommands.ts — /help, /voice, /speak, /models, /model
|
|
8
|
+
* repl/toolHandlers.ts — Tool confirmation, spinner, mutation budget, audit
|
|
9
|
+
* repl/engineLoop.ts — CareerVivid & BYO provider run loops
|
|
10
|
+
*/
|
|
1
11
|
import { CareerVividProxyEngine } from "../../agent/CareerVividProxyEngine.js";
|
|
2
12
|
import { QueryEngine } from "../../agent/QueryEngine.js";
|
|
3
13
|
import { type LLMProvider } from "../../config.js";
|
|
14
|
+
import type { PersonaDefinition } from "./personas.js";
|
|
4
15
|
export declare function printCreditStatus(remaining: number | null, limit?: number | null): void;
|
|
5
16
|
export declare function askLoop(engine: QueryEngine | CareerVividProxyEngine | null, options: {
|
|
6
17
|
verbose?: boolean;
|
|
@@ -12,5 +23,5 @@ export declare function askLoop(engine: QueryEngine | CareerVividProxyEngine | n
|
|
|
12
23
|
jobs?: boolean;
|
|
13
24
|
resume?: boolean;
|
|
14
25
|
coding?: boolean;
|
|
15
|
-
}, selectedProvider: LLMProvider, selectedModel: string, cvApiKey: string | undefined, systemInstruction: string, tools: any[]): Promise<void>;
|
|
26
|
+
}, selectedProvider: LLMProvider, selectedModel: string, cvApiKey: string | undefined, systemInstruction: string, tools: any[], persona: PersonaDefinition): Promise<void>;
|
|
16
27
|
//# sourceMappingURL=repl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUvD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAsBtF;AA+DD,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,GAAG,sBAAsB,GAAG,IAAI,EACnD,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,EACD,gBAAgB,EAAE,WAAW,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,GAAG,EAAE,EACZ,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA4Kf"}
|