careervivid 2.1.21 → 2.1.22

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.
@@ -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,3 +1,13 @@
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";
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAS7G,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAwBtF;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,GAAG,sBAAsB,GAAG,IAAI,EACnD,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9K,gBAAgB,EAAE,WAAW,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,GAAG,EAAE,GACX,OAAO,CAAC,IAAI,CAAC,CAkwBf"}
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;AAanD,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,GACX,OAAO,CAAC,IAAI,CAAC,CA4Kf"}