@hasna/assistants 1.1.25 → 1.1.27

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/index.js CHANGED
@@ -21563,9 +21563,10 @@ class NativeHookRegistry {
21563
21563
  config = {};
21564
21564
  register(hook) {
21565
21565
  const eventHooks = this.hooks.get(hook.event) || [];
21566
- eventHooks.push(hook);
21567
- eventHooks.sort((a, b) => a.priority - b.priority);
21568
- this.hooks.set(hook.event, eventHooks);
21566
+ const deduped = eventHooks.filter((existing) => existing.id !== hook.id);
21567
+ deduped.push(hook);
21568
+ deduped.sort((a, b) => a.priority - b.priority);
21569
+ this.hooks.set(hook.event, deduped);
21569
21570
  }
21570
21571
  getHooks(event) {
21571
21572
  return this.hooks.get(event) || [];
@@ -72917,16 +72918,28 @@ class BudgetTracker {
72917
72918
  case "assistant":
72918
72919
  if (idOrAssistant) {
72919
72920
  this.assistantUsages.set(idOrAssistant, newUsage);
72921
+ } else {
72922
+ this.assistantUsages.clear();
72920
72923
  }
72921
72924
  break;
72922
72925
  case "swarm":
72923
72926
  this.swarmUsage = newUsage;
72924
72927
  break;
72925
72928
  case "project":
72926
- const projectId = idOrAssistant || this.activeProjectId;
72927
- if (projectId) {
72928
- this.projectUsages.set(projectId, newUsage);
72929
- this.saveProjectState(projectId, newUsage);
72929
+ if (idOrAssistant) {
72930
+ this.projectUsages.set(idOrAssistant, newUsage);
72931
+ this.saveProjectState(idOrAssistant, newUsage);
72932
+ break;
72933
+ }
72934
+ if (this.activeProjectId) {
72935
+ this.projectUsages.set(this.activeProjectId, newUsage);
72936
+ this.saveProjectState(this.activeProjectId, newUsage);
72937
+ break;
72938
+ }
72939
+ for (const projectId of this.projectUsages.keys()) {
72940
+ const resetProjectUsage = createEmptyUsage();
72941
+ this.projectUsages.set(projectId, resetProjectUsage);
72942
+ this.saveProjectState(projectId, resetProjectUsage);
72930
72943
  }
72931
72944
  break;
72932
72945
  }
@@ -72936,6 +72949,9 @@ class BudgetTracker {
72936
72949
  this.sessionUsage = createEmptyUsage();
72937
72950
  this.assistantUsages.clear();
72938
72951
  this.swarmUsage = createEmptyUsage();
72952
+ for (const projectId of this.projectUsages.keys()) {
72953
+ this.saveProjectState(projectId, createEmptyUsage());
72954
+ }
72939
72955
  this.projectUsages.clear();
72940
72956
  this.saveState();
72941
72957
  }
@@ -73034,6 +73050,22 @@ var init_tracker = __esm(() => {
73034
73050
  });
73035
73051
 
73036
73052
  // packages/core/src/budget/tools.ts
73053
+ function normalizeScope2(value) {
73054
+ return String(value ?? "").trim().toLowerCase();
73055
+ }
73056
+ function isBudgetToolScope(value) {
73057
+ return BUDGET_TOOL_SCOPES.includes(value);
73058
+ }
73059
+ function isBudgetResetScope(value) {
73060
+ return value === "all" || isBudgetToolScope(value);
73061
+ }
73062
+ function parseLimitValue(value) {
73063
+ const numeric = Number(value);
73064
+ if (!Number.isFinite(numeric) || numeric < 0) {
73065
+ return null;
73066
+ }
73067
+ return numeric === 0 ? undefined : numeric;
73068
+ }
73037
73069
  function createBudgetToolExecutors(getBudgetTracker) {
73038
73070
  return {
73039
73071
  budget_status: async (input) => {
@@ -73041,7 +73073,11 @@ function createBudgetToolExecutors(getBudgetTracker) {
73041
73073
  if (!tracker) {
73042
73074
  return "Budget tracking is not enabled.";
73043
73075
  }
73044
- const scope = String(input.scope || "session");
73076
+ const scopeInput = normalizeScope2(input.scope || "session");
73077
+ if (!isBudgetToolScope(scopeInput)) {
73078
+ return `Invalid scope: ${scopeInput || "(empty)"}. Use "session", "swarm", or "project".`;
73079
+ }
73080
+ const scope = scopeInput;
73045
73081
  const status = tracker.checkBudget(scope);
73046
73082
  const lines = [];
73047
73083
  lines.push(`## Budget Status (${scope})`);
@@ -73096,53 +73132,51 @@ function createBudgetToolExecutors(getBudgetTracker) {
73096
73132
  if (!tracker) {
73097
73133
  return "Budget tracking is not enabled.";
73098
73134
  }
73099
- const scope = String(input.scope || "session");
73100
- const config = tracker.getConfig();
73101
- let limitsKey;
73102
- switch (scope) {
73103
- case "session":
73104
- limitsKey = "sessionLimits";
73105
- break;
73106
- case "swarm":
73107
- limitsKey = "swarmLimits";
73108
- break;
73109
- case "project":
73110
- limitsKey = "projectLimits";
73111
- break;
73112
- default:
73113
- return `Invalid scope: ${scope}. Use "session", "swarm", or "project".`;
73135
+ const scopeInput = normalizeScope2(input.scope || "session");
73136
+ if (!isBudgetToolScope(scopeInput)) {
73137
+ return `Invalid scope: ${scopeInput || "(empty)"}. Use "session", "swarm", or "project".`;
73114
73138
  }
73139
+ const scope = scopeInput;
73140
+ const config = tracker.getConfig();
73115
73141
  const updates = {};
73116
- if (input.maxInputTokens !== undefined)
73117
- updates.maxInputTokens = Number(input.maxInputTokens);
73118
- if (input.maxOutputTokens !== undefined)
73119
- updates.maxOutputTokens = Number(input.maxOutputTokens);
73120
- if (input.maxTotalTokens !== undefined)
73121
- updates.maxTotalTokens = Number(input.maxTotalTokens);
73122
- if (input.maxLlmCalls !== undefined)
73123
- updates.maxLlmCalls = Number(input.maxLlmCalls);
73124
- if (input.maxToolCalls !== undefined)
73125
- updates.maxToolCalls = Number(input.maxToolCalls);
73126
- if (input.maxDurationMs !== undefined)
73127
- updates.maxDurationMs = Number(input.maxDurationMs);
73128
- if (Object.keys(updates).length === 0) {
73142
+ const providedFields = [];
73143
+ const invalidFields = [];
73144
+ for (const field of LIMIT_FIELDS) {
73145
+ if (input[field] === undefined)
73146
+ continue;
73147
+ providedFields.push(field);
73148
+ const parsed = parseLimitValue(input[field]);
73149
+ if (parsed === null) {
73150
+ invalidFields.push(field);
73151
+ continue;
73152
+ }
73153
+ updates[field] = parsed;
73154
+ }
73155
+ if (providedFields.length === 0) {
73129
73156
  return "No limits specified. Provide at least one limit to update.";
73130
73157
  }
73158
+ if (invalidFields.length > 0) {
73159
+ return `Invalid limit values for: ${invalidFields.join(", ")}. Values must be numbers >= 0 (0 = unlimited).`;
73160
+ }
73131
73161
  tracker.updateConfig({
73132
- [limitsKey]: {
73133
- ...config[limitsKey],
73162
+ [scope]: {
73163
+ ...config[scope] || {},
73134
73164
  ...updates
73135
73165
  }
73136
73166
  });
73167
+ const applied = Object.fromEntries(providedFields.map((field) => [field, updates[field] ?? "unlimited"]));
73137
73168
  return `Budget limits updated for ${scope} scope:
73138
- ${JSON.stringify(updates, null, 2)}`;
73169
+ ${JSON.stringify(applied, null, 2)}`;
73139
73170
  },
73140
73171
  budget_reset: async (input) => {
73141
73172
  const tracker = getBudgetTracker();
73142
73173
  if (!tracker) {
73143
73174
  return "Budget tracking is not enabled.";
73144
73175
  }
73145
- const scope = String(input.scope || "session");
73176
+ const scope = normalizeScope2(input.scope || "session");
73177
+ if (!isBudgetResetScope(scope)) {
73178
+ return `Invalid scope: ${scope || "(empty)"}. Use "session", "swarm", "project", or "all".`;
73179
+ }
73146
73180
  if (scope === "all") {
73147
73181
  tracker.resetAll();
73148
73182
  return "All budget counters have been reset.";
@@ -73158,8 +73192,17 @@ function registerBudgetTools(registry, getBudgetTracker) {
73158
73192
  registry.register(tool, executors[tool.name]);
73159
73193
  }
73160
73194
  }
73161
- var budgetStatusTool, budgetGetTool, budgetSetTool, budgetResetTool, budgetTools;
73195
+ var BUDGET_TOOL_SCOPES, LIMIT_FIELDS, budgetStatusTool, budgetGetTool, budgetSetTool, budgetResetTool, budgetTools;
73162
73196
  var init_tools4 = __esm(() => {
73197
+ BUDGET_TOOL_SCOPES = ["session", "swarm", "project"];
73198
+ LIMIT_FIELDS = [
73199
+ "maxInputTokens",
73200
+ "maxOutputTokens",
73201
+ "maxTotalTokens",
73202
+ "maxLlmCalls",
73203
+ "maxToolCalls",
73204
+ "maxDurationMs"
73205
+ ];
73163
73206
  budgetStatusTool = {
73164
73207
  name: "budget_status",
73165
73208
  description: "Get current budget status showing usage vs limits for the specified scope (session, swarm, or project).",
@@ -73231,8 +73274,8 @@ var init_tools4 = __esm(() => {
73231
73274
  properties: {
73232
73275
  scope: {
73233
73276
  type: "string",
73234
- description: 'Budget scope to reset: "session", "swarm", or "all"',
73235
- enum: ["session", "swarm", "all"]
73277
+ description: 'Budget scope to reset: "session", "swarm", "project", or "all"',
73278
+ enum: ["session", "swarm", "project", "all"]
73236
73279
  }
73237
73280
  },
73238
73281
  required: ["scope"]
@@ -78300,6 +78343,7 @@ class BuiltinCommands {
78300
78343
  registerAll(loader) {
78301
78344
  loader.register(this.helpCommand(loader));
78302
78345
  loader.register(this.aboutCommand());
78346
+ loader.register(this.docsCommand());
78303
78347
  loader.register(this.clearCommand());
78304
78348
  loader.register(this.newCommand());
78305
78349
  loader.register(this.sessionCommand());
@@ -78347,6 +78391,8 @@ class BuiltinCommands {
78347
78391
  loader.register(this.tasksCommand());
78348
78392
  loader.register(this.setupCommand());
78349
78393
  loader.register(this.exitCommand());
78394
+ loader.register(this.diffCommand());
78395
+ loader.register(this.undoCommand());
78350
78396
  }
78351
78397
  aboutCommand() {
78352
78398
  return {
@@ -78360,15 +78406,93 @@ class BuiltinCommands {
78360
78406
  **About Hasna**
78361
78407
 
78362
78408
  `;
78363
- message += `Hasna is a company that wants to make AI more useful for everyone.
78409
+ message += `Hasna is on a mission to make AI more useful to everyone.
78410
+ `;
78411
+ message += `We build tools that bring the power of AI into your everyday workflow \u2014 no expertise required.
78364
78412
  `;
78365
78413
  message += `Website: hasna.com
78366
78414
  `;
78367
78415
  message += `
78368
- **About assistants**
78416
+ **About Hasna Assistants**
78417
+
78418
+ `;
78419
+ message += `Hasna Assistants is a general-purpose AI assistant that lives in your terminal.
78420
+ `;
78421
+ message += `It connects natively to 100+ tools \u2014 email, calendars, databases, cloud storage, CRMs, and more \u2014 so you can get things done without switching apps.
78422
+
78423
+ `;
78424
+ message += `What you can do:
78425
+ `;
78426
+ message += `- Ask questions and get answers in plain language
78427
+ `;
78428
+ message += `- Automate repetitive tasks across your tools
78429
+ `;
78430
+ message += `- Read, write, and manage files on your machine
78431
+ `;
78432
+ message += `- Run multi-step workflows with built-in skills
78433
+ `;
78434
+ message += `- Schedule commands to run on a timer
78435
+ `;
78436
+ message += `- Collaborate with multiple AI agents via swarm mode
78369
78437
 
78370
78438
  `;
78371
- message += `assistants is a terminal-first AI environment for connecting tools, running tasks, and collaborating with agents.
78439
+ message += `Whether you are a developer, a founder, or just someone who wants AI to handle the boring stuff \u2014 Assistants is built for you.
78440
+ `;
78441
+ context.emit("text", message);
78442
+ context.emit("done");
78443
+ return { handled: true };
78444
+ }
78445
+ };
78446
+ }
78447
+ docsCommand() {
78448
+ return {
78449
+ name: "docs",
78450
+ description: "Open docs panel in terminal or print full usage guide",
78451
+ builtin: true,
78452
+ selfHandled: true,
78453
+ content: "",
78454
+ handler: async (_args, context) => {
78455
+ let message = `
78456
+ **assistants Documentation**
78457
+
78458
+ `;
78459
+ message += "In the terminal app, `/docs` opens an interactive documentation panel with keyboard navigation.\n\n";
78460
+ message += `**Quick Start**
78461
+ `;
78462
+ message += " 1. Run `/init` in a project.\n";
78463
+ message += " 2. Run `/onboarding` to select provider, model, and API key setup.\n";
78464
+ message += " 3. Start work with `/new` and inspect status via `/status`, `/tokens`, and `/cost`.\n\n";
78465
+ message += `**Core Workflow**
78466
+ `;
78467
+ message += ` - Sessions keep history, tool calls, context, and model state.
78468
+ `;
78469
+ message += " - `/sessions` switches sessions.\n";
78470
+ message += " - `/compact` summarizes long context.\n";
78471
+ message += " - `/resume` recovers interrupted work.\n\n";
78472
+ message += `**Configuration and Models**
78473
+ `;
78474
+ message += " - `/model` opens interactive model selection.\n";
78475
+ message += " - `/config` manages user/project/local config.\n";
78476
+ message += " - `/memory`, `/context`, `/hooks`, and `/guardrails` control behavior and safety.\n\n";
78477
+ message += `**Workspaces and Projects**
78478
+ `;
78479
+ message += " - `/workspace` switches isolated workspace state.\n";
78480
+ message += " - `/projects` and `/plans` manage project scope and plan execution.\n\n";
78481
+ message += `**Resources and Operations**
78482
+ `;
78483
+ message += " - `/wallet`, `/secrets`, and `/budgets` manage cards, secrets, and limits.\n";
78484
+ message += " - `/tasks`, `/schedules`, `/jobs`, `/orders`, `/heartbeat`, and `/logs` manage operations.\n\n";
78485
+ message += `**Collaboration**
78486
+ `;
78487
+ message += " - `/assistants`, `/identity`, `/messages`, `/channels`, `/people`, `/telephony`.\n\n";
78488
+ message += `**Voice**
78489
+ `;
78490
+ message += " - `/voice`, `/listen`, `/talk`, `/say`.\n\n";
78491
+ message += `**Storage**
78492
+ `;
78493
+ message += " - Project data: `.assistants/`\n";
78494
+ message += " - User/global data: `~/.assistants/`\n";
78495
+ message += ` - Workspace switching isolates sessions, assistants, settings, and resource state.
78372
78496
  `;
78373
78497
  context.emit("text", message);
78374
78498
  context.emit("done");
@@ -86888,6 +87012,140 @@ ${repoUrl}/issues/new
86888
87012
  }
86889
87013
  };
86890
87014
  }
87015
+ diffCommand() {
87016
+ return {
87017
+ name: "diff",
87018
+ description: "Show git diff of current changes (supports --staged, <file>)",
87019
+ builtin: true,
87020
+ selfHandled: true,
87021
+ content: "",
87022
+ handler: async (args, context) => {
87023
+ const { exec } = await import("child_process");
87024
+ const { promisify } = await import("util");
87025
+ const execAsync = promisify(exec);
87026
+ const parts = splitArgs(args);
87027
+ try {
87028
+ let diffCmd = "git diff";
87029
+ let statCmd = "git diff --stat";
87030
+ if (parts.includes("--staged") || parts.includes("--cached")) {
87031
+ diffCmd += " --staged";
87032
+ statCmd += " --staged";
87033
+ const fileArgs = parts.filter((p) => p !== "--staged" && p !== "--cached");
87034
+ if (fileArgs.length > 0) {
87035
+ const files = fileArgs.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ");
87036
+ diffCmd += ` -- ${files}`;
87037
+ statCmd += ` -- ${files}`;
87038
+ }
87039
+ } else if (parts.length > 0) {
87040
+ const files = parts.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ");
87041
+ diffCmd += ` -- ${files}`;
87042
+ statCmd += ` -- ${files}`;
87043
+ }
87044
+ const opts = { cwd: context.cwd, maxBuffer: 1048576 };
87045
+ const [statResult, diffResult] = await Promise.all([
87046
+ execAsync(statCmd, opts).catch(() => ({ stdout: "" })),
87047
+ execAsync(diffCmd, opts).catch(() => ({ stdout: "" }))
87048
+ ]);
87049
+ const statOutput = (statResult.stdout || "").trim();
87050
+ const diffOutput = (diffResult.stdout || "").trim();
87051
+ if (!statOutput && !diffOutput) {
87052
+ context.emit("text", `
87053
+ No changes detected.
87054
+ `);
87055
+ context.emit("done");
87056
+ return { handled: true };
87057
+ }
87058
+ let message = `
87059
+ **Git Diff**
87060
+
87061
+ `;
87062
+ if (statOutput) {
87063
+ message += "**Summary:**\n```\n" + statOutput + "\n```\n\n";
87064
+ }
87065
+ if (diffOutput) {
87066
+ message += "**Changes:**\n```diff\n" + diffOutput + "\n```\n";
87067
+ }
87068
+ context.emit("text", message);
87069
+ } catch {
87070
+ context.emit("text", `
87071
+ Not a git repository or git not available.
87072
+ `);
87073
+ }
87074
+ context.emit("done");
87075
+ return { handled: true };
87076
+ }
87077
+ };
87078
+ }
87079
+ undoCommand() {
87080
+ return {
87081
+ name: "undo",
87082
+ description: "Revert uncommitted changes (file, all, or show preview)",
87083
+ builtin: true,
87084
+ selfHandled: true,
87085
+ content: "",
87086
+ handler: async (args, context) => {
87087
+ const { exec } = await import("child_process");
87088
+ const { promisify } = await import("util");
87089
+ const execAsync = promisify(exec);
87090
+ const parts = splitArgs(args);
87091
+ const opts = { cwd: context.cwd };
87092
+ try {
87093
+ if (parts.length === 0) {
87094
+ const { stdout: stdout2 } = await execAsync("git diff --stat", opts);
87095
+ const statOutput = stdout2.trim();
87096
+ if (!statOutput) {
87097
+ context.emit("text", `
87098
+ No uncommitted changes to undo.
87099
+ `);
87100
+ context.emit("done");
87101
+ return { handled: true };
87102
+ }
87103
+ let message = "\n**Uncommitted Changes:**\n```\n" + statOutput + "\n```\n\n";
87104
+ message += "Use `/undo <file>` to revert a specific file\n";
87105
+ message += "Use `/undo all` to revert all changes\n";
87106
+ context.emit("text", message);
87107
+ context.emit("done");
87108
+ return { handled: true };
87109
+ }
87110
+ if (parts[0] === "all") {
87111
+ const { stdout: stdout2 } = await execAsync("git diff --stat", opts);
87112
+ const statOutput = stdout2.trim();
87113
+ if (!statOutput) {
87114
+ context.emit("text", `
87115
+ No uncommitted changes to undo.
87116
+ `);
87117
+ context.emit("done");
87118
+ return { handled: true };
87119
+ }
87120
+ await execAsync("git checkout -- .", opts);
87121
+ context.emit("text", "\n**Reverted all uncommitted changes:**\n```\n" + statOutput + "\n```\n");
87122
+ context.emit("done");
87123
+ return { handled: true };
87124
+ }
87125
+ const escaped = parts.map((f) => `'${f.replace(/'/g, "'\\''")}'`).join(" ");
87126
+ const { stdout } = await execAsync(`git diff --stat -- ${escaped}`, opts);
87127
+ const checkOutput = stdout.trim();
87128
+ if (!checkOutput) {
87129
+ context.emit("text", `
87130
+ No changes found for: ${parts.join(" ")}
87131
+ `);
87132
+ context.emit("done");
87133
+ return { handled: true };
87134
+ }
87135
+ await execAsync(`git checkout -- ${escaped}`, opts);
87136
+ context.emit("text", `
87137
+ **Reverted:** ${parts.join(" ")}
87138
+ `);
87139
+ } catch {
87140
+ context.emit("text", `
87141
+ Not a git repository or git not available.
87142
+ `);
87143
+ }
87144
+ context.emit("done");
87145
+ return { handled: true };
87146
+ }
87147
+ };
87148
+ }
86891
87149
  async resolveProject(context, target) {
86892
87150
  const byId = await readProject(context.cwd, target);
86893
87151
  if (byId)
@@ -86918,7 +87176,7 @@ ${repoUrl}/issues/new
86918
87176
  context.setProjectContext(projectContext);
86919
87177
  }
86920
87178
  }
86921
- var VERSION2 = "1.1.25";
87179
+ var VERSION2 = "1.1.27";
86922
87180
  var init_builtin = __esm(async () => {
86923
87181
  init_src2();
86924
87182
  init_store();
@@ -92083,14 +92341,22 @@ var init_conventions = __esm(() => {
92083
92341
  });
92084
92342
 
92085
92343
  // packages/core/src/heartbeat/auto-schedule-hook.ts
92086
- async function autoScheduleHeartbeatHandler(_input, context) {
92087
- const heartbeatCfg = context.config?.heartbeat;
92344
+ function resolveHeartbeatConfig(input, context) {
92345
+ const inputHeartbeat = input.heartbeat;
92346
+ if (inputHeartbeat && typeof inputHeartbeat === "object") {
92347
+ return inputHeartbeat;
92348
+ }
92349
+ return context.config?.heartbeat;
92350
+ }
92351
+ async function autoScheduleHeartbeatHandler(input, context) {
92352
+ const heartbeatCfg = resolveHeartbeatConfig(input, context);
92088
92353
  if (!heartbeatCfg?.autonomous)
92089
92354
  return null;
92090
92355
  const scheduleId = heartbeatScheduleId(context.sessionId);
92091
92356
  try {
92092
92357
  const existing = await getSchedule(context.cwd, scheduleId);
92093
- if (existing && existing.status === "active") {
92358
+ const hasValidNextRunAt = Number.isFinite(existing?.nextRunAt);
92359
+ if (existing && existing.status === "active" && hasValidNextRunAt) {
92094
92360
  return null;
92095
92361
  }
92096
92362
  const maxSleep = heartbeatCfg.maxSleepMs ?? DEFAULT_MAX_SLEEP_MS;
@@ -92135,11 +92401,13 @@ var init_auto_schedule_hook = __esm(async () => {
92135
92401
  async function ensureWatchdogSchedule(cwd, sessionId, intervalMs = DEFAULT_WATCHDOG_INTERVAL_MS) {
92136
92402
  const scheduleId = watchdogScheduleId(sessionId);
92137
92403
  const existing = await getSchedule(cwd, scheduleId);
92138
- if (existing && existing.status === "active") {
92404
+ const existingHasValidNextRunAt = Number.isFinite(existing?.nextRunAt);
92405
+ if (existing && existing.status === "active" && existingHasValidNextRunAt) {
92139
92406
  return;
92140
92407
  }
92141
92408
  const legacy = await getSchedule(cwd, WATCHDOG_SCHEDULE_ID);
92142
- if (legacy && legacy.status === "active" && legacy.sessionId === sessionId) {
92409
+ const legacyHasValidNextRunAt = Number.isFinite(legacy?.nextRunAt);
92410
+ if (legacy && legacy.status === "active" && legacy.sessionId === sessionId && legacyHasValidNextRunAt) {
92143
92411
  return;
92144
92412
  }
92145
92413
  const intervalSeconds = Math.max(60, Math.round(intervalMs / 1000));
@@ -92170,24 +92438,33 @@ var init_watchdog = __esm(async () => {
92170
92438
 
92171
92439
  // packages/core/src/heartbeat/install-skills.ts
92172
92440
  import { join as join30 } from "path";
92173
- async function writeSkillIfMissing(dir, skillName, content) {
92174
- const { mkdir: mkdir10, writeFile: writeFile8, access } = await import("fs/promises");
92441
+ async function writeSkillIfNeeded(dir, skillName, content) {
92442
+ const { mkdir: mkdir10, writeFile: writeFile8, readFile: readFile11 } = await import("fs/promises");
92175
92443
  const skillDir = join30(dir, `skill-${skillName}`);
92176
92444
  const skillFile = join30(skillDir, "SKILL.md");
92177
- try {
92178
- await access(skillFile);
92179
- return false;
92180
- } catch {}
92181
92445
  await mkdir10(skillDir, { recursive: true });
92182
- await writeFile8(skillFile, content, "utf-8");
92183
- return true;
92446
+ try {
92447
+ const existing = await readFile11(skillFile, "utf-8");
92448
+ if (existing === content) {
92449
+ return false;
92450
+ }
92451
+ const hasLegacyPatterns = LEGACY_SKILL_PATTERNS.some((pattern) => pattern.test(existing));
92452
+ if (!hasLegacyPatterns) {
92453
+ return false;
92454
+ }
92455
+ await writeFile8(skillFile, content, "utf-8");
92456
+ return true;
92457
+ } catch {
92458
+ await writeFile8(skillFile, content, "utf-8");
92459
+ return true;
92460
+ }
92184
92461
  }
92185
92462
  async function installHeartbeatSkills() {
92186
92463
  const sharedSkillsDir = join30(getConfigDir(), "shared", "skills");
92187
92464
  const installed = [];
92188
92465
  const results = await Promise.all([
92189
- writeSkillIfMissing(sharedSkillsDir, "main-loop", MAIN_LOOP_SKILL),
92190
- writeSkillIfMissing(sharedSkillsDir, "watchdog", WATCHDOG_SKILL)
92466
+ writeSkillIfNeeded(sharedSkillsDir, "main-loop", MAIN_LOOP_SKILL),
92467
+ writeSkillIfNeeded(sharedSkillsDir, "watchdog", WATCHDOG_SKILL)
92191
92468
  ]);
92192
92469
  if (results[0])
92193
92470
  installed.push("main-loop");
@@ -92216,8 +92493,8 @@ You are running as an autonomous heartbeat turn. This is a scheduled wakeup \u20
92216
92493
  - \`memory_save agent.state.lastActions "..."\` with a brief summary of what you did this turn.
92217
92494
  - \`memory_save agent.state.pending "..."\` with any items still pending.
92218
92495
  7. **Schedule next heartbeat** \u2014 choose when you should wake up next based on urgency:
92219
- - Delete the old heartbeat schedule: \`schedule_delete heartbeat-{SESSION_ID}\`
92220
- - Create a new schedule: \`schedule_create\` with \`actionType: "message"\`, \`message: "/main-loop"\`, and either \`at\` (one-shot) or \`cron\` + \`startImmediately: true\` (recurring).
92496
+ - Delete the old heartbeat schedule: call \`schedule\` with \`{ action: "delete", id: "heartbeat-{SESSION_ID}" }\`
92497
+ - Create a new schedule: call \`schedule\` with \`action: "create"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and either \`at\` (one-shot) or \`cron\` + \`startImmediately: true\` (recurring).
92221
92498
  - Save your reasoning: \`memory_save agent.heartbeat.intention "..."\`
92222
92499
  8. **Record timestamp** \u2014 \`memory_save agent.heartbeat.last\` with the current ISO timestamp.
92223
92500
 
@@ -92237,7 +92514,7 @@ You are running as an autonomous heartbeat turn. This is a scheduled wakeup \u20
92237
92514
  name: watchdog
92238
92515
  description: Safety-net watchdog \u2014 checks if the heartbeat is healthy and forces a wakeup if overdue.
92239
92516
  user-invocable: false
92240
- allowed-tools: memory_recall, memory_save, schedule_create, schedule_delete, schedules_list
92517
+ allowed-tools: memory_recall, memory_save, schedule
92241
92518
  ---
92242
92519
 
92243
92520
  ## Watchdog Check
@@ -92249,14 +92526,20 @@ You are the watchdog. Your only job is to verify the heartbeat is running and fo
92249
92526
  1. Read \`memory_recall agent.heartbeat.last\` to get the last heartbeat timestamp.
92250
92527
  2. Read \`memory_recall agent.heartbeat.next\` to get the expected next heartbeat time.
92251
92528
  3. If the last heartbeat is more than **double** the expected interval overdue:
92252
- - Call \`schedules_list\` to check if a heartbeat schedule exists.
92529
+ - Call \`schedule\` with \`{ action: "list" }\` to check if a heartbeat schedule exists.
92253
92530
  - If no active heartbeat schedule exists, create one that fires immediately:
92254
- \`schedule_create\` with \`actionType: "message"\`, \`message: "/main-loop"\`, and \`cron: "* * * * *"\` + \`startImmediately: true\`.
92531
+ call \`schedule\` with \`action: "create"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and \`cron: "* * * * *"\` + \`startImmediately: true\`.
92255
92532
  - Save \`memory_save agent.heartbeat.intention "Watchdog forced wakeup \u2014 heartbeat was overdue."\`
92256
92533
  4. If the heartbeat is healthy, do nothing.
92257
- `;
92534
+ `, LEGACY_SKILL_PATTERNS;
92258
92535
  var init_install_skills = __esm(async () => {
92259
92536
  await init_config();
92537
+ LEGACY_SKILL_PATTERNS = [
92538
+ /`schedule_create`/,
92539
+ /`schedule_delete`/,
92540
+ /`schedules_list`/,
92541
+ /allowed-tools:\s*.*schedule_create/i
92542
+ ];
92260
92543
  });
92261
92544
 
92262
92545
  // packages/core/src/heartbeat/index.ts
@@ -186336,15 +186619,6 @@ var init_loop = __esm(async () => {
186336
186619
  if (heartbeatCfg?.autonomous) {
186337
186620
  nativeHookRegistry.register(createAutoScheduleHeartbeatHook());
186338
186621
  installHeartbeatSkills().catch(() => {});
186339
- nativeHookRegistry.setConfig({
186340
- ...nativeHookRegistry.getConfig(),
186341
- heartbeat: {
186342
- autonomous: heartbeatCfg.autonomous,
186343
- maxSleepMs: heartbeatCfg.maxSleepMs,
186344
- watchdogEnabled: heartbeatCfg.watchdogEnabled,
186345
- watchdogIntervalMs: heartbeatCfg.watchdogIntervalMs
186346
- }
186347
- });
186348
186622
  if (heartbeatCfg.watchdogEnabled) {
186349
186623
  ensureWatchdogSchedule(this.cwd, this.sessionId, heartbeatCfg.watchdogIntervalMs).catch(() => {});
186350
186624
  }
@@ -186380,8 +186654,8 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
186380
186654
  - \`memory_save agent.state.pending "items waiting for follow-up"\`
186381
186655
  - \`memory_save agent.state.lastActions "what you just did"\`
186382
186656
  2. **Schedule your next heartbeat**:
186383
- - Delete old: \`schedule_delete heartbeat-${this.sessionId}\`
186384
- - Create new: \`schedule_create\` with \`actionType: "message"\`, \`message: "/main-loop"\`, and either \`at\` (one-shot) or \`cron\` + \`startImmediately: true\` (recurring)
186657
+ - Delete old: call \`schedule\` with \`{ action: "delete", id: "heartbeat-${this.sessionId}" }\`
186658
+ - Create new: call \`schedule\` with \`action: "create"\`, \`actionType: "message"\`, \`message: "/main-loop"\`, and either \`at\` (one-shot) or \`cron\` + \`startImmediately: true\` (recurring)
186385
186659
  3. **Save goals** when they change: \`memory_save agent.goals "..."\`
186386
186660
 
186387
186661
  ### Timing guidelines
@@ -186635,9 +186909,9 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
186635
186909
  turn++;
186636
186910
  this.cumulativeTurns++;
186637
186911
  if (this.paused) {
186638
- this.onBudgetWarning?.("Budget exceeded - agent paused. Use /budget resume to continue.");
186912
+ this.onBudgetWarning?.("Budget exceeded - agent paused. Use /budgets resume to continue.");
186639
186913
  this.emit({ type: "text", content: `
186640
- [Agent paused - budget exceeded. Use /budget resume to continue.]
186914
+ [Agent paused - budget exceeded. Use /budgets resume to continue.]
186641
186915
  ` });
186642
186916
  await new Promise((resolve5) => {
186643
186917
  this.pauseResolve = resolve5;
@@ -186714,11 +186988,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
186714
186988
  });
186715
186989
  } catch {}
186716
186990
  try {
186717
- await nativeHookRegistry.execute("Stop", {
186718
- session_id: this.sessionId,
186719
- hook_event_name: "Stop",
186720
- cwd: this.cwd
186721
- }, {
186991
+ await nativeHookRegistry.execute("Stop", this.buildNativeStopHookInput(), {
186722
186992
  sessionId: this.sessionId,
186723
186993
  cwd: this.cwd,
186724
186994
  messages: this.context.getMessages()
@@ -186840,11 +187110,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
186840
187110
  if (!scopeContext) {
186841
187111
  return null;
186842
187112
  }
186843
- const result = await nativeHookRegistry.execute("Stop", {
186844
- session_id: this.sessionId,
186845
- hook_event_name: "Stop",
186846
- cwd: this.cwd
186847
- }, {
187113
+ const result = await nativeHookRegistry.execute("Stop", this.buildNativeStopHookInput(), {
186848
187114
  sessionId: this.sessionId,
186849
187115
  cwd: this.cwd,
186850
187116
  messages: this.context.getMessages(),
@@ -186859,6 +187125,20 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
186859
187125
  systemMessage: result.systemMessage
186860
187126
  };
186861
187127
  }
187128
+ buildNativeStopHookInput() {
187129
+ const heartbeatCfg = this.config?.heartbeat;
187130
+ return {
187131
+ session_id: this.sessionId,
187132
+ hook_event_name: "Stop",
187133
+ cwd: this.cwd,
187134
+ heartbeat: heartbeatCfg ? {
187135
+ autonomous: heartbeatCfg.autonomous,
187136
+ maxSleepMs: heartbeatCfg.maxSleepMs,
187137
+ watchdogEnabled: heartbeatCfg.watchdogEnabled,
187138
+ watchdogIntervalMs: heartbeatCfg.watchdogIntervalMs
187139
+ } : undefined
187140
+ };
187141
+ }
186862
187142
  async firePostToolUseFailure(toolCall, resultContent) {
186863
187143
  await this.hookExecutor.execute(this.hookLoader.getHooks("PostToolUseFailure"), {
186864
187144
  session_id: this.sessionId,
@@ -187323,9 +187603,32 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
187323
187603
  setBudgetEnabled: (enabled) => {
187324
187604
  if (this.budgetTracker) {
187325
187605
  this.budgetTracker.setEnabled(enabled);
187326
- } else if (enabled && this.budgetConfig) {
187327
- this.budgetTracker = new BudgetTracker(this.sessionId, this.budgetConfig);
187328
- this.budgetTracker.setEnabled(true);
187606
+ this.budgetConfig = this.budgetTracker.getConfig();
187607
+ return;
187608
+ }
187609
+ if (!enabled) {
187610
+ if (this.budgetConfig) {
187611
+ this.budgetConfig = {
187612
+ ...this.budgetConfig,
187613
+ enabled: false
187614
+ };
187615
+ }
187616
+ return;
187617
+ }
187618
+ const seed = this.budgetConfig || DEFAULT_BUDGET_CONFIG;
187619
+ this.budgetConfig = {
187620
+ ...DEFAULT_BUDGET_CONFIG,
187621
+ ...seed,
187622
+ session: { ...DEFAULT_BUDGET_CONFIG.session || {}, ...seed.session || {} },
187623
+ assistant: { ...DEFAULT_BUDGET_CONFIG.assistant || {}, ...seed.assistant || {} },
187624
+ swarm: { ...DEFAULT_BUDGET_CONFIG.swarm || {}, ...seed.swarm || {} },
187625
+ project: { ...DEFAULT_BUDGET_CONFIG.project || {}, ...seed.project || {} },
187626
+ enabled: true
187627
+ };
187628
+ this.budgetTracker = new BudgetTracker(this.sessionId, this.budgetConfig);
187629
+ this.budgetTracker.setEnabled(true);
187630
+ if (this.activeProjectId) {
187631
+ this.budgetTracker.setActiveProject(this.activeProjectId);
187329
187632
  }
187330
187633
  },
187331
187634
  resetBudget: (scope) => {
@@ -187751,7 +188054,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
187751
188054
  this.onBudgetWarning?.("Budget exceeded - stopping assistant");
187752
188055
  this.stop();
187753
188056
  } else if (onExceeded === "pause") {
187754
- this.onBudgetWarning?.("Budget exceeded - pausing (requires /budget resume to continue)");
188057
+ this.onBudgetWarning?.("Budget exceeded - pausing (requires /budgets resume to continue)");
187755
188058
  this.paused = true;
187756
188059
  }
187757
188060
  }
@@ -210481,6 +210784,12 @@ function useSafeInput(handler, options = {}) {
210481
210784
  ` : "\x1B\r";
210482
210785
  }
210483
210786
  const key2 = { ...inkKey };
210787
+ if ((input2 === "m" || input2 === "j") && key2.ctrl && !key2.return) {
210788
+ input2 = input2 === "j" ? `
210789
+ ` : "\r";
210790
+ key2.return = true;
210791
+ key2.ctrl = false;
210792
+ }
210484
210793
  const isReturnInput = input2 === "\r" || input2 === `
210485
210794
  ` || input2 === `\r
210486
210795
  ` || input2 === `
@@ -210610,6 +210919,8 @@ var COMMANDS = [
210610
210919
  { name: "/secrets", description: "manage assistant secrets" },
210611
210920
  { name: "/jobs", description: "manage background jobs" },
210612
210921
  { name: "/docs", description: "interactive app documentation" },
210922
+ { name: "/diff", description: "show git diff of changes" },
210923
+ { name: "/undo", description: "revert uncommitted changes" },
210613
210924
  { name: "/rest", description: "enter rest mode" },
210614
210925
  { name: "/logs", description: "view security event logs" },
210615
210926
  { name: "/verification", description: "scope verification status" },
@@ -210644,6 +210955,7 @@ function isLargePaste(text, thresholds = DEFAULT_PASTE_THRESHOLDS) {
210644
210955
  }
210645
210956
  var Input = import_react28.default.forwardRef(function Input2({
210646
210957
  onSubmit,
210958
+ onStopProcessing,
210647
210959
  isProcessing,
210648
210960
  queueLength = 0,
210649
210961
  commands: commands9,
@@ -210657,7 +210969,8 @@ var Input = import_react28.default.forwardRef(function Input2({
210657
210969
  pasteConfig,
210658
210970
  isRecording = false,
210659
210971
  recordingStatus,
210660
- onStopRecording
210972
+ onStopRecording,
210973
+ onFileSearch
210661
210974
  }, ref) {
210662
210975
  const pasteEnabled = pasteConfig?.enabled !== false;
210663
210976
  const pasteThresholds = pasteConfig?.thresholds ?? DEFAULT_PASTE_THRESHOLDS;
@@ -210712,8 +211025,14 @@ var Input = import_react28.default.forwardRef(function Input2({
210712
211025
  if (value.startsWith("/") && !value.includes(" ")) {
210713
211026
  return "command";
210714
211027
  }
211028
+ if (onFileSearch) {
211029
+ const atMatch = value.match(/(?:^|.*\s)@([^\s]*)$/);
211030
+ if (atMatch) {
211031
+ return "file";
211032
+ }
211033
+ }
210715
211034
  return null;
210716
- }, [value, isAskingUser]);
211035
+ }, [value, isAskingUser, onFileSearch]);
210717
211036
  const filteredCommands = import_react28.useMemo(() => {
210718
211037
  if (autocompleteMode !== "command")
210719
211038
  return [];
@@ -210726,7 +211045,18 @@ var Input = import_react28.default.forwardRef(function Input2({
210726
211045
  const search = value.slice(1).toLowerCase();
210727
211046
  return skills.filter((skill) => skill.name.toLowerCase().startsWith(search));
210728
211047
  }, [value, autocompleteMode, skills]);
210729
- const autocompleteItems = autocompleteMode === "skill" ? filteredSkills : filteredCommands;
211048
+ const fileSearchQuery = import_react28.useMemo(() => {
211049
+ if (autocompleteMode !== "file")
211050
+ return "";
211051
+ const atMatch = value.match(/(?:^|.*\s)@([^\s]*)$/);
211052
+ return atMatch ? atMatch[1] : "";
211053
+ }, [value, autocompleteMode]);
211054
+ const filteredFiles = import_react28.useMemo(() => {
211055
+ if (autocompleteMode !== "file" || !onFileSearch)
211056
+ return [];
211057
+ return onFileSearch(fileSearchQuery);
211058
+ }, [autocompleteMode, fileSearchQuery, onFileSearch]);
211059
+ const autocompleteItems = autocompleteMode === "skill" ? filteredSkills : autocompleteMode === "file" ? filteredFiles.map((f6) => ({ name: f6 })) : filteredCommands;
210730
211060
  import_react28.useEffect(() => {
210731
211061
  if (autocompleteItems.length === 0) {
210732
211062
  setSelectedIndex(0);
@@ -210907,6 +211237,10 @@ var Input = import_react28.default.forwardRef(function Input2({
210907
211237
  return;
210908
211238
  }
210909
211239
  if (key.escape && !isAskingUser) {
211240
+ if (isProcessing && onStopProcessing) {
211241
+ onStopProcessing();
211242
+ return;
211243
+ }
210910
211244
  if (largePaste) {
210911
211245
  setLargePaste(null);
210912
211246
  setShowPastePreview(false);
@@ -210932,8 +211266,15 @@ var Input = import_react28.default.forwardRef(function Input2({
210932
211266
  if (key.tab) {
210933
211267
  if (autocompleteItems.length > 0) {
210934
211268
  const selected = autocompleteItems[selectedIndex] || autocompleteItems[0];
210935
- const nextValue = autocompleteMode === "skill" ? `$${selected.name} ` : `${selected.name} `;
210936
- setValueAndCursor(nextValue);
211269
+ if (autocompleteMode === "file") {
211270
+ const atMatch = value.match(/^(.*(?:^|\s))@[^\s]*$/);
211271
+ const prefix2 = atMatch ? atMatch[1] : "";
211272
+ const nextValue = prefix2 + selected.name + " ";
211273
+ setValueAndCursor(nextValue);
211274
+ } else {
211275
+ const nextValue = autocompleteMode === "skill" ? `$${selected.name} ` : `${selected.name} `;
211276
+ setValueAndCursor(nextValue);
211277
+ }
210937
211278
  return;
210938
211279
  }
210939
211280
  if (isProcessing && value.trim()) {
@@ -211013,12 +211354,6 @@ var Input = import_react28.default.forwardRef(function Input2({
211013
211354
  deleteForward();
211014
211355
  return;
211015
211356
  }
211016
- if (input === "\x1B\r" || input === `\x1B
211017
- `) {
211018
- insertText(`
211019
- `);
211020
- return;
211021
- }
211022
211357
  if (key.return) {
211023
211358
  if (isRecording && onStopRecording) {
211024
211359
  onStopRecording();
@@ -211029,7 +211364,8 @@ var Input = import_react28.default.forwardRef(function Input2({
211029
211364
  setValueAndCursor("");
211030
211365
  return;
211031
211366
  }
211032
- if (key.meta && value.trim()) {
211367
+ if (key.meta && value.trim() && input !== "\x1B\r" && input !== `\x1B
211368
+ `) {
211033
211369
  onSubmit(value, "queue");
211034
211370
  setValueAndCursor("");
211035
211371
  return;
@@ -211068,6 +211404,8 @@ var Input = import_react28.default.forwardRef(function Input2({
211068
211404
  };
211069
211405
  const visibleSkills = getVisibleItems(filteredSkills);
211070
211406
  const visibleCommands = getVisibleItems(filteredCommands);
211407
+ const fileItems = import_react28.useMemo(() => filteredFiles.map((f6) => ({ name: f6 })), [filteredFiles]);
211408
+ const visibleFiles = getVisibleItems(fileItems);
211071
211409
  const layout = buildLayout(value, cursor, textWidth);
211072
211410
  const lines = layout.displayLines;
211073
211411
  const lineCount = value.split(`
@@ -211340,6 +211678,41 @@ var Input = import_react28.default.forwardRef(function Input2({
211340
211678
  ]
211341
211679
  }, undefined, true, undefined, this)
211342
211680
  ]
211681
+ }, undefined, true, undefined, this),
211682
+ autocompleteMode === "file" && filteredFiles.length > 0 && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
211683
+ flexDirection: "column",
211684
+ marginTop: 1,
211685
+ marginLeft: 2,
211686
+ children: [
211687
+ visibleFiles.startIndex > 0 && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text3, {
211688
+ dimColor: true,
211689
+ children: [
211690
+ " \u2191 ",
211691
+ visibleFiles.startIndex,
211692
+ " more above"
211693
+ ]
211694
+ }, undefined, true, undefined, this),
211695
+ visibleFiles.items.map((file, i5) => {
211696
+ const actualIndex = visibleFiles.startIndex + i5;
211697
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
211698
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text3, {
211699
+ color: actualIndex === selectedIndex ? "cyan" : "#5fb3a1",
211700
+ children: [
211701
+ actualIndex === selectedIndex ? "\u25B8 " : " ",
211702
+ file.name
211703
+ ]
211704
+ }, undefined, true, undefined, this)
211705
+ }, file.name, false, undefined, this);
211706
+ }),
211707
+ visibleFiles.startIndex + maxVisible < filteredFiles.length && /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text3, {
211708
+ dimColor: true,
211709
+ children: [
211710
+ " \u2193 ",
211711
+ filteredFiles.length - visibleFiles.startIndex - maxVisible,
211712
+ " more below"
211713
+ ]
211714
+ }, undefined, true, undefined, this)
211715
+ ]
211343
211716
  }, undefined, true, undefined, this)
211344
211717
  ]
211345
211718
  }, undefined, true, undefined, this);
@@ -213778,7 +214151,8 @@ function Status({
213778
214151
  backgroundProcessingCount = 0,
213779
214152
  processingStartTime,
213780
214153
  verboseTools = false,
213781
- recentTools = []
214154
+ recentTools = [],
214155
+ gitBranch
213782
214156
  }) {
213783
214157
  const [elapsed, setElapsed] = import_react30.useState(0);
213784
214158
  const [heartbeatCountdown, setHeartbeatCountdown] = import_react30.useState("");
@@ -213879,7 +214253,8 @@ function Status({
213879
214253
  dimColor: true,
213880
214254
  children: [
213881
214255
  "/help",
213882
- sessionCount && sessionCount > 1 ? " \xB7 Ctrl+]" : ""
214256
+ sessionCount && sessionCount > 1 ? " \xB7 Ctrl+]" : "",
214257
+ gitBranch ? ` \xB7 \u2387 ${gitBranch}` : ""
213883
214258
  ]
213884
214259
  }, undefined, true, undefined, this),
213885
214260
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
@@ -214851,6 +215226,7 @@ function ConnectorsPanel({
214851
215226
  const [loadedConnectors, setLoadedConnectors] = import_react36.useState(new Map);
214852
215227
  const [autoRefreshEntries, setAutoRefreshEntries] = import_react36.useState(new Map);
214853
215228
  const [autoRefreshError, setAutoRefreshError] = import_react36.useState(null);
215229
+ const pendingAuthChecksRef = import_react36.useRef(new Set);
214854
215230
  const filteredConnectors = import_react36.useMemo(() => {
214855
215231
  if (!searchQuery.trim()) {
214856
215232
  return connectors;
@@ -214874,24 +215250,6 @@ function ConnectorsPanel({
214874
215250
  }
214875
215251
  }
214876
215252
  }, [initialConnector, filteredConnectors]);
214877
- import_react36.useEffect(() => {
214878
- const loadStatuses = async () => {
214879
- const results = await Promise.all(connectors.map(async (connector) => {
214880
- try {
214881
- const status = await onCheckAuth(connector);
214882
- return { name: connector.name, status };
214883
- } catch {
214884
- return { name: connector.name, status: { authenticated: false, error: "Failed to check" } };
214885
- }
214886
- }));
214887
- const statusMap = new Map;
214888
- for (const { name: name2, status } of results) {
214889
- statusMap.set(name2, status);
214890
- }
214891
- setAuthStatuses(statusMap);
214892
- };
214893
- loadStatuses();
214894
- }, [connectors, onCheckAuth]);
214895
215253
  const loadAutoRefreshEntries = import_react36.useCallback(async () => {
214896
215254
  try {
214897
215255
  const manager = ConnectorAutoRefreshManager.getInstance();
@@ -214991,6 +215349,10 @@ function ConnectorsPanel({
214991
215349
  }, [mode, loadCommandHelp]);
214992
215350
  useSafeInput((input, key) => {
214993
215351
  if (isSearching) {
215352
+ if (input === "q" || input === "Q") {
215353
+ onClose();
215354
+ return;
215355
+ }
214994
215356
  if (key.escape) {
214995
215357
  if (searchQuery) {
214996
215358
  setSearchQuery("");
@@ -215049,7 +215411,7 @@ function ConnectorsPanel({
215049
215411
  if (key.upArrow) {
215050
215412
  if (mode === "list" && filteredConnectors.length > 0) {
215051
215413
  setConnectorIndex((prev) => prev === 0 ? filteredConnectors.length - 1 : prev - 1);
215052
- } else if (mode === "detail") {
215414
+ } else if (mode === "detail" && currentCommands.length > 0) {
215053
215415
  setCommandIndex((prev) => prev === 0 ? currentCommands.length - 1 : prev - 1);
215054
215416
  }
215055
215417
  return;
@@ -215057,7 +215419,7 @@ function ConnectorsPanel({
215057
215419
  if (key.downArrow) {
215058
215420
  if (mode === "list" && filteredConnectors.length > 0) {
215059
215421
  setConnectorIndex((prev) => prev === filteredConnectors.length - 1 ? 0 : prev + 1);
215060
- } else if (mode === "detail") {
215422
+ } else if (mode === "detail" && currentCommands.length > 0) {
215061
215423
  setCommandIndex((prev) => prev === currentCommands.length - 1 ? 0 : prev + 1);
215062
215424
  }
215063
215425
  return;
@@ -215071,10 +215433,6 @@ function ConnectorsPanel({
215071
215433
  }
215072
215434
  return;
215073
215435
  }
215074
- if (mode === "list" && input && /^[a-zA-Z]$/.test(input)) {
215075
- setIsSearching(true);
215076
- setSearchQuery(input);
215077
- }
215078
215436
  });
215079
215437
  const getStatusIcon = (status) => {
215080
215438
  if (!status)
@@ -215088,6 +215446,45 @@ function ConnectorsPanel({
215088
215446
  const connectorRange = import_react36.useMemo(() => getVisibleRange(safeConnectorIndex, filteredConnectors.length), [safeConnectorIndex, filteredConnectors.length]);
215089
215447
  const commandRange = import_react36.useMemo(() => getVisibleRange(commandIndex, currentCommands.length), [commandIndex, currentCommands.length]);
215090
215448
  const visibleConnectors = filteredConnectors.slice(connectorRange.start, connectorRange.end);
215449
+ import_react36.useEffect(() => {
215450
+ const targets = mode === "list" ? visibleConnectors : currentConnector ? [currentConnector] : [];
215451
+ if (targets.length === 0) {
215452
+ return;
215453
+ }
215454
+ let cancelled = false;
215455
+ const loadStatuses = async () => {
215456
+ for (const connector of targets) {
215457
+ const name2 = connector.name;
215458
+ if (authStatuses.has(name2) || pendingAuthChecksRef.current.has(name2)) {
215459
+ continue;
215460
+ }
215461
+ pendingAuthChecksRef.current.add(name2);
215462
+ try {
215463
+ let status;
215464
+ try {
215465
+ status = await onCheckAuth(connector);
215466
+ } catch {
215467
+ status = { authenticated: false, error: "Failed to check" };
215468
+ }
215469
+ if (cancelled)
215470
+ continue;
215471
+ setAuthStatuses((prev) => {
215472
+ if (prev.has(name2))
215473
+ return prev;
215474
+ const next = new Map(prev);
215475
+ next.set(name2, status);
215476
+ return next;
215477
+ });
215478
+ } finally {
215479
+ pendingAuthChecksRef.current.delete(name2);
215480
+ }
215481
+ }
215482
+ };
215483
+ loadStatuses();
215484
+ return () => {
215485
+ cancelled = true;
215486
+ };
215487
+ }, [authStatuses, currentConnector, mode, onCheckAuth, visibleConnectors]);
215091
215488
  if (connectors.length === 0) {
215092
215489
  return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Box_default, {
215093
215490
  flexDirection: "column",
@@ -226654,16 +227051,16 @@ function JobsPanel({ manager, onClose }) {
226654
227051
  if (!job || isWorking)
226655
227052
  return;
226656
227053
  if (!isActiveStatus(job.status)) {
226657
- setStatusMessage(`Job ${job.id} is ${job.status}. Only pending/running jobs can be cancelled.`);
227054
+ setStatusMessage(`Job ${job.id} is ${job.status}. Only pending/running jobs can be killed.`);
226658
227055
  return;
226659
227056
  }
226660
227057
  setIsWorking(true);
226661
227058
  try {
226662
227059
  const cancelled = await manager.cancelJob(job.id);
226663
227060
  if (cancelled) {
226664
- setStatusMessage(`Cancelled ${job.id}.`);
227061
+ setStatusMessage(`Killed ${job.id}.`);
226665
227062
  } else {
226666
- setStatusMessage(`Could not cancel ${job.id}.`);
227063
+ setStatusMessage(`Could not kill ${job.id}.`);
226667
227064
  }
226668
227065
  await refreshJobs(false);
226669
227066
  } catch (err) {
@@ -226672,6 +227069,38 @@ function JobsPanel({ manager, onClose }) {
226672
227069
  setIsWorking(false);
226673
227070
  }
226674
227071
  }, [isWorking, manager, refreshJobs]);
227072
+ const killAllActiveJobs = import_react50.useCallback(async () => {
227073
+ if (isWorking)
227074
+ return;
227075
+ const activeJobs = jobs2.filter((job) => isActiveStatus(job.status));
227076
+ if (activeJobs.length === 0) {
227077
+ setStatusMessage("No active jobs to kill.");
227078
+ return;
227079
+ }
227080
+ setIsWorking(true);
227081
+ try {
227082
+ let killed = 0;
227083
+ for (const job of activeJobs) {
227084
+ try {
227085
+ const cancelled = await manager.cancelJob(job.id);
227086
+ if (cancelled)
227087
+ killed += 1;
227088
+ } catch {}
227089
+ }
227090
+ if (killed === 0) {
227091
+ setStatusMessage("Could not kill any active jobs.");
227092
+ } else if (killed === activeJobs.length) {
227093
+ setStatusMessage(`Killed ${killed} active job${killed === 1 ? "" : "s"}.`);
227094
+ } else {
227095
+ setStatusMessage(`Killed ${killed}/${activeJobs.length} active jobs.`);
227096
+ }
227097
+ await refreshJobs(false);
227098
+ } catch (err) {
227099
+ setError(err instanceof Error ? err.message : String(err));
227100
+ } finally {
227101
+ setIsWorking(false);
227102
+ }
227103
+ }, [isWorking, jobs2, manager, refreshJobs]);
226675
227104
  const openDetail = import_react50.useCallback(() => {
226676
227105
  const selected = filteredJobs[selectedIndex];
226677
227106
  if (!selected)
@@ -226694,7 +227123,11 @@ function JobsPanel({ manager, onClose }) {
226694
227123
  refreshJobs(true);
226695
227124
  return;
226696
227125
  }
226697
- if (input === "x" || input === "c") {
227126
+ if (input === "K") {
227127
+ killAllActiveJobs();
227128
+ return;
227129
+ }
227130
+ if (input === "x" || input === "c" || input === "d") {
226698
227131
  cancelJob(detailJob);
226699
227132
  }
226700
227133
  return;
@@ -226748,7 +227181,11 @@ function JobsPanel({ manager, onClose }) {
226748
227181
  refreshJobs(true);
226749
227182
  return;
226750
227183
  }
226751
- if (input === "x" || input === "c") {
227184
+ if (input === "K") {
227185
+ killAllActiveJobs();
227186
+ return;
227187
+ }
227188
+ if (input === "x" || input === "c" || input === "d") {
226752
227189
  cancelJob(filteredJobs[selectedIndex] || null);
226753
227190
  }
226754
227191
  });
@@ -226786,7 +227223,9 @@ function JobsPanel({ manager, onClose }) {
226786
227223
  children: [
226787
227224
  "(",
226788
227225
  jobs2.length,
226789
- " total)"
227226
+ " total, ",
227227
+ jobs2.filter((job) => isActiveStatus(job.status)).length,
227228
+ " active)"
226790
227229
  ]
226791
227230
  }, undefined, true, undefined, this)
226792
227231
  ]
@@ -226907,7 +227346,7 @@ function JobsPanel({ manager, onClose }) {
226907
227346
  marginTop: 1,
226908
227347
  children: /* @__PURE__ */ jsx_dev_runtime27.jsxDEV(Text3, {
226909
227348
  color: "gray",
226910
- children: "x/c cancel r refresh esc back q close"
227349
+ children: "d/x/c kill selected K kill all active r refresh esc back q close"
226911
227350
  }, undefined, false, undefined, this)
226912
227351
  }, undefined, false, undefined, this)
226913
227352
  ]
@@ -227027,7 +227466,7 @@ function JobsPanel({ manager, onClose }) {
227027
227466
  marginTop: 1,
227028
227467
  children: /* @__PURE__ */ jsx_dev_runtime27.jsxDEV(Text3, {
227029
227468
  color: "gray",
227030
- children: "j/k or arrows move enter view x/c cancel r refresh 1/2/3 filter q close"
227469
+ children: "j/k or arrows move enter view d/x/c kill selected K kill all active r refresh 1/2/3 filter q close"
227031
227470
  }, undefined, false, undefined, this)
227032
227471
  }, undefined, false, undefined, this)
227033
227472
  ]
@@ -227116,7 +227555,7 @@ var DOCS_SECTIONS = [
227116
227555
  content: [
227117
227556
  "Use /tasks for queued local tasks with priority and pause/resume controls.",
227118
227557
  "Use /schedules for recurring command execution with next-run visibility.",
227119
- "Use /jobs for background connector/tool jobs. Cancel jobs directly in the panel.",
227558
+ "Use /jobs for background connector/tool jobs. Kill jobs directly in the panel.",
227120
227559
  "Use /orders for interactive order/store workflows (tabs, table navigation, detail views).",
227121
227560
  "Use /logs for security events and /heartbeat for recurring assistant runs."
227122
227561
  ]
@@ -228835,20 +229274,29 @@ function GuardrailsPanel({
228835
229274
  return;
228836
229275
  }
228837
229276
  if (mode === "policies") {
229277
+ const policiesListSize = policies.length + 1;
228838
229278
  if (key.upArrow) {
228839
- setSelectedIndex((prev) => prev === 0 ? Math.max(0, totalItems - 1) : prev - 1);
229279
+ setSelectedIndex((prev) => prev === 0 ? policiesListSize - 1 : prev - 1);
228840
229280
  return;
228841
229281
  }
228842
229282
  if (key.downArrow) {
228843
- setSelectedIndex((prev) => prev >= totalItems - 1 ? 0 : prev + 1);
229283
+ setSelectedIndex((prev) => prev >= policiesListSize - 1 ? 0 : prev + 1);
228844
229284
  return;
228845
229285
  }
228846
229286
  if (key.return) {
228847
- const policy = policies[selectedIndex];
228848
- if (policy) {
228849
- setDetailPolicyId(policy.id);
228850
- setRuleIndex(0);
228851
- setMode("policy-detail");
229287
+ if (selectedIndex === policies.length) {
229288
+ setCreateName("");
229289
+ setCreateScopeIdx(2);
229290
+ setCreateActionIdx(0);
229291
+ setCreateField(0);
229292
+ setMode("policy-create");
229293
+ } else {
229294
+ const policy = policies[selectedIndex];
229295
+ if (policy) {
229296
+ setDetailPolicyId(policy.id);
229297
+ setRuleIndex(0);
229298
+ setMode("policy-detail");
229299
+ }
228852
229300
  }
228853
229301
  return;
228854
229302
  }
@@ -229407,54 +229855,66 @@ function GuardrailsPanel({
229407
229855
  paddingX: 1,
229408
229856
  height: Math.min(12, policies.length + 2),
229409
229857
  overflowY: "hidden",
229410
- children: policies.length === 0 ? /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229411
- paddingY: 1,
229412
- children: /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229413
- dimColor: true,
229414
- children: "No policies. Press n to create one."
229415
- }, undefined, false, undefined, this)
229416
- }, undefined, false, undefined, this) : policies.map((policy, index) => {
229417
- const isSelected = index === selectedIndex;
229418
- const scopeColor = SCOPE_COLORS[policy.scope] || "white";
229419
- return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229858
+ children: [
229859
+ policies.length === 0 ? /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229860
+ paddingY: 1,
229420
229861
  children: /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229421
- inverse: isSelected,
229422
- children: [
229423
- isSelected ? ">" : " ",
229424
- " ",
229425
- /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229426
- color: policy.enabled ? "green" : "red",
229427
- children: [
229428
- "[",
229429
- policy.enabled ? "on " : "off",
229430
- "]"
229431
- ]
229432
- }, undefined, true, undefined, this),
229433
- " ",
229434
- /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229435
- bold: isSelected,
229436
- children: (policy.name || policy.id).slice(0, 20).padEnd(20)
229437
- }, undefined, false, undefined, this),
229438
- " ",
229439
- /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229440
- color: scopeColor,
229441
- children: policy.scope.padEnd(10)
229442
- }, undefined, false, undefined, this),
229443
- " ",
229444
- /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229445
- dimColor: true,
229446
- children: policy.location
229447
- }, undefined, false, undefined, this)
229448
- ]
229449
- }, undefined, true, undefined, this)
229450
- }, policy.id, false, undefined, this);
229451
- })
229452
- }, undefined, false, undefined, this),
229862
+ dimColor: true,
229863
+ children: "No policies. Press n to create one."
229864
+ }, undefined, false, undefined, this)
229865
+ }, undefined, false, undefined, this) : policies.map((policy, index) => {
229866
+ const isSelected = index === selectedIndex;
229867
+ const scopeColor = SCOPE_COLORS[policy.scope] || "white";
229868
+ return /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229869
+ children: /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229870
+ inverse: isSelected,
229871
+ children: [
229872
+ isSelected ? ">" : " ",
229873
+ " ",
229874
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229875
+ color: policy.enabled ? "green" : "red",
229876
+ children: [
229877
+ "[",
229878
+ policy.enabled ? "on " : "off",
229879
+ "]"
229880
+ ]
229881
+ }, undefined, true, undefined, this),
229882
+ " ",
229883
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229884
+ bold: isSelected,
229885
+ children: (policy.name || policy.id).slice(0, 20).padEnd(20)
229886
+ }, undefined, false, undefined, this),
229887
+ " ",
229888
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229889
+ color: scopeColor,
229890
+ children: policy.scope.padEnd(10)
229891
+ }, undefined, false, undefined, this),
229892
+ " ",
229893
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229894
+ dimColor: true,
229895
+ children: policy.location
229896
+ }, undefined, false, undefined, this)
229897
+ ]
229898
+ }, undefined, true, undefined, this)
229899
+ }, policy.id, false, undefined, this);
229900
+ }),
229901
+ /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229902
+ marginTop: policies.length > 0 ? 1 : 0,
229903
+ paddingY: 0,
229904
+ children: /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229905
+ inverse: selectedIndex === policies.length,
229906
+ dimColor: selectedIndex !== policies.length,
229907
+ color: selectedIndex === policies.length ? "cyan" : undefined,
229908
+ children: "+ New policy (n)"
229909
+ }, undefined, false, undefined, this)
229910
+ }, undefined, false, undefined, this)
229911
+ ]
229912
+ }, undefined, true, undefined, this),
229453
229913
  /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Box_default, {
229454
229914
  marginTop: 1,
229455
229915
  children: /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text3, {
229456
229916
  dimColor: true,
229457
- children: "[n]ew [e]nable [d]isable [Enter] detail [b]ack [q]uit | \u2191\u2193 navigate"
229917
+ children: "[e]nable [d]isable [Enter] detail [b]ack [q]uit | \u2191\u2193 navigate"
229458
229918
  }, undefined, false, undefined, this)
229459
229919
  }, undefined, false, undefined, this)
229460
229920
  ]
@@ -230616,7 +231076,7 @@ function BudgetsPanel({
230616
231076
  const editSessionStatus = showingActiveProfile ? sessionStatus : createDraftStatus("session", activeOrDraftProfileForEdit?.config.session);
230617
231077
  const editSwarmStatus = showingActiveProfile ? swarmStatus : createDraftStatus("swarm", activeOrDraftProfileForEdit?.config.swarm);
230618
231078
  import_react57.useEffect(() => {
230619
- setSelectedIndex((prev) => Math.min(prev, Math.max(0, profiles.length - 1)));
231079
+ setSelectedIndex((prev) => Math.min(prev, profiles.length));
230620
231080
  }, [profiles.length]);
230621
231081
  import_react57.useEffect(() => {
230622
231082
  if (!editingProfileId)
@@ -230701,7 +231161,9 @@ function BudgetsPanel({
230701
231161
  return;
230702
231162
  }
230703
231163
  if (key.return) {
230704
- if (selectedProfile) {
231164
+ if (selectedIndex === profiles.length) {
231165
+ startCreateForm();
231166
+ } else if (selectedProfile) {
230705
231167
  onSelectProfile(selectedProfile.id);
230706
231168
  }
230707
231169
  return;
@@ -230711,15 +231173,11 @@ function BudgetsPanel({
230711
231173
  return;
230712
231174
  }
230713
231175
  if (key.upArrow) {
230714
- if (profiles.length === 0)
230715
- return;
230716
- setSelectedIndex((prev) => prev === 0 ? profiles.length - 1 : prev - 1);
231176
+ setSelectedIndex((prev) => prev === 0 ? profiles.length : prev - 1);
230717
231177
  return;
230718
231178
  }
230719
231179
  if (key.downArrow) {
230720
- if (profiles.length === 0)
230721
- return;
230722
- setSelectedIndex((prev) => prev === profiles.length - 1 ? 0 : prev + 1);
231180
+ setSelectedIndex((prev) => prev === profiles.length ? 0 : prev + 1);
230723
231181
  return;
230724
231182
  }
230725
231183
  }, { isActive: true });
@@ -230972,34 +231430,46 @@ function BudgetsPanel({
230972
231430
  borderStyle: "round",
230973
231431
  borderColor: "gray",
230974
231432
  paddingX: 1,
230975
- children: profiles.length === 0 ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
230976
- paddingY: 1,
230977
- children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text3, {
230978
- dimColor: true,
230979
- children: "No budget profiles. Press n to create one."
230980
- }, undefined, false, undefined, this)
230981
- }, undefined, false, undefined, this) : profiles.map((profile, index) => {
230982
- const isSelected = index === selectedIndex;
230983
- const isActive = profile.id === activeProfileId;
230984
- return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
231433
+ children: [
231434
+ profiles.length === 0 ? /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
231435
+ paddingY: 1,
231436
+ children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text3, {
231437
+ dimColor: true,
231438
+ children: "No budget profiles. Press n to create one."
231439
+ }, undefined, false, undefined, this)
231440
+ }, undefined, false, undefined, this) : profiles.map((profile, index) => {
231441
+ const isSelected = index === selectedIndex;
231442
+ const isActive = profile.id === activeProfileId;
231443
+ return /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
231444
+ paddingY: 0,
231445
+ children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text3, {
231446
+ inverse: isSelected,
231447
+ color: isActive ? "green" : undefined,
231448
+ dimColor: !isSelected && !isActive,
231449
+ children: [
231450
+ isActive ? "*" : " ",
231451
+ " ",
231452
+ index + 1,
231453
+ ". ",
231454
+ profile.name.padEnd(22),
231455
+ " ",
231456
+ profile.description || ""
231457
+ ]
231458
+ }, undefined, true, undefined, this)
231459
+ }, profile.id, false, undefined, this);
231460
+ }),
231461
+ /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
231462
+ marginTop: profiles.length > 0 ? 1 : 0,
230985
231463
  paddingY: 0,
230986
231464
  children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text3, {
230987
- inverse: isSelected,
230988
- color: isActive ? "green" : undefined,
230989
- dimColor: !isSelected && !isActive,
230990
- children: [
230991
- isActive ? "*" : " ",
230992
- " ",
230993
- index + 1,
230994
- ". ",
230995
- profile.name.padEnd(22),
230996
- " ",
230997
- profile.description || ""
230998
- ]
230999
- }, undefined, true, undefined, this)
231000
- }, profile.id, false, undefined, this);
231001
- })
231002
- }, undefined, false, undefined, this),
231465
+ inverse: selectedIndex === profiles.length,
231466
+ dimColor: selectedIndex !== profiles.length,
231467
+ color: selectedIndex === profiles.length ? "cyan" : undefined,
231468
+ children: "+ New profile (n)"
231469
+ }, undefined, false, undefined, this)
231470
+ }, undefined, false, undefined, this)
231471
+ ]
231472
+ }, undefined, true, undefined, this),
231003
231473
  selectedProfile && /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Box_default, {
231004
231474
  marginTop: 1,
231005
231475
  children: /* @__PURE__ */ jsx_dev_runtime32.jsxDEV(Text3, {
@@ -236360,7 +236830,7 @@ function SecretsPanel({
236360
236830
  setIsProcessing(false);
236361
236831
  }
236362
236832
  };
236363
- const normalizeScope2 = (rawScope) => {
236833
+ const normalizeScope3 = (rawScope) => {
236364
236834
  const scope = rawScope.trim().toLowerCase();
236365
236835
  if (!scope)
236366
236836
  return "assistant";
@@ -236369,7 +236839,7 @@ function SecretsPanel({
236369
236839
  return null;
236370
236840
  };
236371
236841
  const normalizeAddInput = (form) => {
236372
- const scope = normalizeScope2(String(form.scope));
236842
+ const scope = normalizeScope3(String(form.scope));
236373
236843
  if (!scope) {
236374
236844
  return null;
236375
236845
  }
@@ -236390,7 +236860,7 @@ function SecretsPanel({
236390
236860
  return;
236391
236861
  }
236392
236862
  if (currentAddField.key === "scope") {
236393
- const normalizedScope = normalizeScope2(currentValue);
236863
+ const normalizedScope = normalizeScope3(currentValue);
236394
236864
  if (!normalizedScope) {
236395
236865
  setStatusMessage('Scope must be "assistant" or "global".');
236396
236866
  return;
@@ -238870,6 +239340,18 @@ function ResumePanel({
238870
239340
  }, undefined, true, undefined, this);
238871
239341
  }
238872
239342
 
239343
+ // packages/terminal/src/components/queueUtils.ts
239344
+ function takeNextQueuedMessage(queue, sessionId) {
239345
+ const next = queue.find((msg) => msg.sessionId === sessionId);
239346
+ if (!next) {
239347
+ return { next: null, remaining: queue };
239348
+ }
239349
+ return {
239350
+ next,
239351
+ remaining: queue.filter((msg) => msg.id !== next.id)
239352
+ };
239353
+ }
239354
+
238873
239355
  // packages/terminal/src/output/sanitize.ts
238874
239356
  var CLEAR_SCREEN_SEQUENCE = "\x1B[2J\x1B[3J\x1B[H";
238875
239357
  var CLEAR_SCREEN_TOKEN = "__ASSISTANTS_CLEAR_SCREEN__";
@@ -239256,6 +239738,7 @@ function App2({ cwd: cwd2, version: version3 }) {
239256
239738
  const [heartbeatState, setHeartbeatState] = import_react72.useState();
239257
239739
  const [identityInfo, setIdentityInfo] = import_react72.useState();
239258
239740
  const [verboseTools, setVerboseTools] = import_react72.useState(false);
239741
+ const [gitBranch, setGitBranch] = import_react72.useState();
239259
239742
  const [askUserState, setAskUserState] = import_react72.useState(null);
239260
239743
  const [processingStartTime, setProcessingStartTime] = import_react72.useState();
239261
239744
  const [currentTurnTokens, setCurrentTurnTokens] = import_react72.useState(0);
@@ -239269,6 +239752,42 @@ function App2({ cwd: cwd2, version: version3 }) {
239269
239752
  const pttRecorderRef = import_react72.useRef(null);
239270
239753
  const [skills, setSkills] = import_react72.useState([]);
239271
239754
  const [commands9, setCommands] = import_react72.useState([]);
239755
+ const fileListCacheRef = import_react72.useRef({ files: [], timestamp: 0 });
239756
+ const searchFiles = import_react72.useCallback((query) => {
239757
+ const cache7 = fileListCacheRef.current;
239758
+ const now2 = Date.now();
239759
+ if (now2 - cache7.timestamp > 30000 || cache7.files.length === 0) {
239760
+ try {
239761
+ const { execSync } = __require("child_process");
239762
+ let output;
239763
+ try {
239764
+ output = execSync("git ls-files --cached --others --exclude-standard 2>/dev/null", {
239765
+ cwd: cwd2,
239766
+ encoding: "utf-8",
239767
+ maxBuffer: 1024 * 1024,
239768
+ timeout: 3000
239769
+ });
239770
+ } catch {
239771
+ output = execSync('find . -type f -not -path "*/node_modules/*" -not -path "*/.git/*" -maxdepth 5 2>/dev/null | head -1000', {
239772
+ cwd: cwd2,
239773
+ encoding: "utf-8",
239774
+ maxBuffer: 1024 * 1024,
239775
+ timeout: 3000
239776
+ });
239777
+ }
239778
+ cache7.files = output.trim().split(`
239779
+ `).filter(Boolean).slice(0, 2000);
239780
+ cache7.timestamp = now2;
239781
+ } catch {
239782
+ return [];
239783
+ }
239784
+ }
239785
+ if (!query)
239786
+ return cache7.files.slice(0, 20);
239787
+ const lower = query.toLowerCase();
239788
+ const matches = cache7.files.filter((f6) => f6.toLowerCase().includes(lower));
239789
+ return matches.slice(0, 20);
239790
+ }, [cwd2]);
239272
239791
  const lastCtrlCRef = import_react72.useRef(0);
239273
239792
  const [showExitHint, setShowExitHint] = import_react72.useState(false);
239274
239793
  const responseRef = import_react72.useRef("");
@@ -239277,6 +239796,8 @@ function App2({ cwd: cwd2, version: version3 }) {
239277
239796
  const activityLogRef = import_react72.useRef([]);
239278
239797
  const skipNextDoneRef = import_react72.useRef(false);
239279
239798
  const isProcessingRef = import_react72.useRef(isProcessing);
239799
+ const currentToolCallRef = import_react72.useRef(currentToolCall);
239800
+ const hasPendingToolsRef = import_react72.useRef(false);
239280
239801
  const inputRef = import_react72.useRef(null);
239281
239802
  const isListeningRef = import_react72.useRef(isListening);
239282
239803
  const listenLoopRef = import_react72.useRef({
@@ -239357,6 +239878,9 @@ function App2({ cwd: cwd2, version: version3 }) {
239357
239878
  import_react72.useEffect(() => {
239358
239879
  isProcessingRef.current = isProcessing;
239359
239880
  }, [isProcessing]);
239881
+ import_react72.useEffect(() => {
239882
+ currentToolCallRef.current = currentToolCall;
239883
+ }, [currentToolCall]);
239360
239884
  import_react72.useEffect(() => {
239361
239885
  isListeningRef.current = isListening;
239362
239886
  }, [isListening]);
@@ -239370,6 +239894,14 @@ function App2({ cwd: cwd2, version: version3 }) {
239370
239894
  processingStartTimeRef.current = now2;
239371
239895
  }
239372
239896
  }, [isProcessing, processingStartTime]);
239897
+ import_react72.useEffect(() => {
239898
+ const { exec: exec3 } = __require("child_process");
239899
+ exec3("git branch --show-current 2>/dev/null", { cwd: cwd2 }, (err, stdout2) => {
239900
+ if (!err && stdout2?.trim()) {
239901
+ setGitBranch(stdout2.trim());
239902
+ }
239903
+ });
239904
+ }, [cwd2]);
239373
239905
  const buildFullResponse = import_react72.useCallback(() => {
239374
239906
  const parts = activityLogRef.current.filter((entry) => entry.type === "text" && entry.content).map((entry) => entry.content);
239375
239907
  if (responseRef.current.trim()) {
@@ -240048,7 +240580,7 @@ function App2({ cwd: cwd2, version: version3 }) {
240048
240580
  const pendingIndex = pendingSendsRef.current.findIndex((entry) => entry.sessionId === active.id);
240049
240581
  if (pendingIndex !== -1) {
240050
240582
  const [started] = pendingSendsRef.current.splice(pendingIndex, 1);
240051
- if (started?.mode === "inline") {
240583
+ if (started) {
240052
240584
  setInlinePending((prev) => prev.filter((msg) => msg.id !== started.id));
240053
240585
  }
240054
240586
  }
@@ -240702,17 +241234,10 @@ function App2({ cwd: cwd2, version: version3 }) {
240702
241234
  const activeSession2 = registryRef.current.getActiveSession();
240703
241235
  if (!activeSession2 || !activeSessionId)
240704
241236
  return;
240705
- let nextMessage;
240706
- setMessageQueue((prev) => {
240707
- const idx = prev.findIndex((msg) => msg.sessionId === activeSessionId);
240708
- if (idx === -1) {
240709
- return prev;
240710
- }
240711
- nextMessage = prev[idx];
240712
- return [...prev.slice(0, idx), ...prev.slice(idx + 1)];
240713
- });
241237
+ const { next: nextMessage, remaining } = takeNextQueuedMessage(messageQueue, activeSessionId);
240714
241238
  if (!nextMessage)
240715
241239
  return;
241240
+ setMessageQueue(remaining);
240716
241241
  const userMessage = {
240717
241242
  id: nextMessage.id,
240718
241243
  role: "user",
@@ -240741,7 +241266,6 @@ function App2({ cwd: cwd2, version: version3 }) {
240741
241266
  setIsProcessing(true);
240742
241267
  isProcessingRef.current = true;
240743
241268
  registryRef.current.setProcessing(activeSession2.id, true);
240744
- pendingSendsRef.current.push({ id: nextMessage.id, sessionId: activeSessionId, mode: "queued" });
240745
241269
  try {
240746
241270
  await activeSession2.client.send(nextMessage.content);
240747
241271
  } catch (err) {
@@ -240750,8 +241274,9 @@ function App2({ cwd: cwd2, version: version3 }) {
240750
241274
  setIsProcessing(false);
240751
241275
  isProcessingRef.current = false;
240752
241276
  registryRef.current.setProcessing(activeSession2.id, false);
241277
+ setQueueFlushTrigger((prev) => prev + 1);
240753
241278
  }
240754
- }, [activeSessionId, clearPendingSend]);
241279
+ }, [activeSessionId, clearPendingSend, messageQueue]);
240755
241280
  const activeQueue = activeSessionId ? messageQueue.filter((msg) => msg.sessionId === activeSessionId) : [];
240756
241281
  const activeInline = activeSessionId ? inlinePending.filter((msg) => msg.sessionId === activeSessionId) : [];
240757
241282
  const queuedMessageIds = import_react72.useMemo(() => new Set(activeQueue.filter((msg) => msg.mode === "queued").map((msg) => msg.id)), [activeQueue]);
@@ -240811,6 +241336,9 @@ function App2({ cwd: cwd2, version: version3 }) {
240811
241336
  }
240812
241337
  return false;
240813
241338
  }, [activityLog]);
241339
+ import_react72.useEffect(() => {
241340
+ hasPendingToolsRef.current = hasPendingTools;
241341
+ }, [hasPendingTools]);
240814
241342
  const isBusy = isProcessing || hasPendingTools;
240815
241343
  const listenHints = import_react72.useMemo(() => {
240816
241344
  if (!isListening)
@@ -240890,6 +241418,26 @@ function App2({ cwd: cwd2, version: version3 }) {
240890
241418
  setError(err instanceof Error ? err.message : "Failed to create session");
240891
241419
  }
240892
241420
  }, [cwd2, createAndActivateSession]);
241421
+ const stopActiveProcessing = import_react72.useCallback((status = "stopped") => {
241422
+ const active = registryRef.current.getActiveSession();
241423
+ if (!active)
241424
+ return false;
241425
+ const sessionProcessing = active.isProcessing;
241426
+ const shouldStopNow = isProcessingRef.current || hasPendingToolsRef.current || sessionProcessing || Boolean(currentToolCallRef.current);
241427
+ if (!shouldStopNow)
241428
+ return false;
241429
+ active.client.stop();
241430
+ const finalized = finalizeResponse2(status);
241431
+ if (finalized) {
241432
+ skipNextDoneRef.current = true;
241433
+ }
241434
+ resetTurnState();
241435
+ registryRef.current.setProcessing(active.id, false);
241436
+ setIsProcessing(false);
241437
+ isProcessingRef.current = false;
241438
+ setQueueFlushTrigger((prev) => prev + 1);
241439
+ return true;
241440
+ }, [finalizeResponse2, resetTurnState]);
240893
241441
  useSafeInput((input, key) => {
240894
241442
  if (isListeningRef.current && key.ctrl && input === "l") {
240895
241443
  stopListening();
@@ -240910,18 +241458,7 @@ function App2({ cwd: cwd2, version: version3 }) {
240910
241458
  if (hasAsk) {
240911
241459
  cancelAskUser("Cancelled by user", activeSessionId);
240912
241460
  }
240913
- const sessionProcessing = activeSession?.isProcessing ?? false;
240914
- if ((isProcessing || hasPendingTools || sessionProcessing || currentToolCall) && activeSession) {
240915
- activeSession.client.stop();
240916
- const finalized = finalizeResponse2("stopped");
240917
- if (finalized) {
240918
- skipNextDoneRef.current = true;
240919
- }
240920
- resetTurnState();
240921
- registryRef.current.setProcessing(activeSession.id, false);
240922
- setIsProcessing(false);
240923
- isProcessingRef.current = false;
240924
- setQueueFlushTrigger((prev) => prev + 1);
241461
+ if (stopActiveProcessing("stopped")) {
240925
241462
  lastCtrlCRef.current = 0;
240926
241463
  setShowExitHint(false);
240927
241464
  return;
@@ -240950,18 +241487,8 @@ function App2({ cwd: cwd2, version: version3 }) {
240950
241487
  if (activeSessionId && askUserStateRef.current.has(activeSessionId)) {
240951
241488
  cancelAskUser("Cancelled by user", activeSessionId);
240952
241489
  }
240953
- const sessionProcessing = activeSession?.isProcessing ?? false;
240954
- if ((isProcessing || hasPendingTools || sessionProcessing || currentToolCall) && activeSession) {
240955
- activeSession.client.stop();
240956
- const finalized = finalizeResponse2("stopped");
240957
- if (finalized) {
240958
- skipNextDoneRef.current = true;
240959
- }
240960
- resetTurnState();
240961
- registryRef.current.setProcessing(activeSession.id, false);
240962
- setIsProcessing(false);
240963
- isProcessingRef.current = false;
240964
- setQueueFlushTrigger((prev) => prev + 1);
241490
+ if (stopActiveProcessing("stopped")) {
241491
+ return;
240965
241492
  }
240966
241493
  }
240967
241494
  if (key.ctrl && input === "a") {
@@ -240972,7 +241499,7 @@ function App2({ cwd: cwd2, version: version3 }) {
240972
241499
  openBudgetsPanel();
240973
241500
  return;
240974
241501
  }
240975
- if (key.ctrl && input === "m") {
241502
+ if (key.ctrl && input === "m" && !key.return) {
240976
241503
  const messagesManager = registry2.getActiveSession()?.client.getMessagesManager?.();
240977
241504
  if (messagesManager) {
240978
241505
  messagesManager.list({ limit: 50 }).then((msgs) => {
@@ -241455,7 +241982,7 @@ function App2({ cwd: cwd2, version: version3 }) {
241455
241982
  timestamp: now()
241456
241983
  }
241457
241984
  ]);
241458
- pendingSendsRef.current.push({ id: inlineId, sessionId: activeSessionId, mode: "inline" });
241985
+ pendingSendsRef.current.push({ id: inlineId, sessionId: activeSessionId });
241459
241986
  try {
241460
241987
  await activeSession.client.send(trimmedInput);
241461
241988
  } catch (err) {
@@ -241464,17 +241991,8 @@ function App2({ cwd: cwd2, version: version3 }) {
241464
241991
  }
241465
241992
  return;
241466
241993
  }
241467
- if (mode === "interrupt" && isProcessing) {
241468
- activeSession.client.stop();
241469
- const finalized = finalizeResponse2("interrupted");
241470
- if (finalized) {
241471
- skipNextDoneRef.current = true;
241472
- }
241473
- resetTurnState();
241474
- setIsProcessing(false);
241475
- isProcessingRef.current = false;
241476
- registry2.setProcessing(activeSession.id, false);
241477
- setQueueFlushTrigger((prev) => prev + 1);
241994
+ if (mode === "interrupt" && isBusy) {
241995
+ stopActiveProcessing("interrupted");
241478
241996
  await new Promise((r6) => setTimeout(r6, 100));
241479
241997
  }
241480
241998
  const userMessage = {
@@ -241516,6 +242034,7 @@ function App2({ cwd: cwd2, version: version3 }) {
241516
242034
  }, [
241517
242035
  activeSession,
241518
242036
  isProcessing,
242037
+ isBusy,
241519
242038
  registry2,
241520
242039
  sessions,
241521
242040
  handleNewSession,
@@ -241527,7 +242046,8 @@ function App2({ cwd: cwd2, version: version3 }) {
241527
242046
  submitAskAnswer,
241528
242047
  clearPendingSend,
241529
242048
  startListening,
241530
- stopListening
242049
+ stopListening,
242050
+ stopActiveProcessing
241531
242051
  ]);
241532
242052
  import_react72.useEffect(() => {
241533
242053
  sendListenMessageRef.current = (text) => {
@@ -243292,6 +243812,9 @@ ${msg.body || msg.preview}`);
243292
243812
  /* @__PURE__ */ jsx_dev_runtime48.jsxDEV(Input, {
243293
243813
  ref: inputRef,
243294
243814
  onSubmit: handleSubmit,
243815
+ onStopProcessing: () => {
243816
+ stopActiveProcessing("stopped");
243817
+ },
243295
243818
  isProcessing: isBusy,
243296
243819
  queueLength: activeQueue.length + inlineCount,
243297
243820
  commands: commands9,
@@ -243303,7 +243826,8 @@ ${msg.body || msg.preview}`);
243303
243826
  assistantName: identityInfo?.assistant?.name || undefined,
243304
243827
  isRecording: pttRecording,
243305
243828
  recordingStatus: pttStatus,
243306
- onStopRecording: togglePushToTalk
243829
+ onStopRecording: togglePushToTalk,
243830
+ onFileSearch: searchFiles
243307
243831
  }, undefined, false, undefined, this),
243308
243832
  /* @__PURE__ */ jsx_dev_runtime48.jsxDEV(Status, {
243309
243833
  isProcessing: isBusy,
@@ -243318,7 +243842,8 @@ ${msg.body || msg.preview}`);
243318
243842
  sessionCount,
243319
243843
  backgroundProcessingCount,
243320
243844
  processingStartTime,
243321
- verboseTools
243845
+ verboseTools,
243846
+ gitBranch
243322
243847
  }, undefined, false, undefined, this)
243323
243848
  ]
243324
243849
  }, undefined, true, undefined, this);
@@ -243766,7 +244291,7 @@ Interactive Mode:
243766
244291
  // packages/terminal/src/index.tsx
243767
244292
  var jsx_dev_runtime49 = __toESM(require_jsx_dev_runtime(), 1);
243768
244293
  setRuntime(bunRuntime);
243769
- var VERSION4 = "1.1.25";
244294
+ var VERSION4 = "1.1.27";
243770
244295
  var SYNC_START = "\x1B[?2026h";
243771
244296
  var SYNC_END = "\x1B[?2026l";
243772
244297
  function enableSynchronizedOutput() {
@@ -243906,4 +244431,4 @@ export {
243906
244431
  main
243907
244432
  };
243908
244433
 
243909
- //# debugId=57A038F79EFFC02464756E2164756E21
244434
+ //# debugId=463A5E72A8326CA464756E2164756E21