@rallycry/conveyor-agent 7.0.5 → 7.0.7

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.
@@ -9,12 +9,16 @@ import {
9
9
  // src/connection/agent-connection.ts
10
10
  import { io } from "socket.io-client";
11
11
  var EVENT_BATCH_MS = 500;
12
- var AgentConnection = class {
12
+ var AgentConnection = class _AgentConnection {
13
13
  socket = null;
14
14
  config;
15
15
  eventBuffer = [];
16
16
  flushTimer = null;
17
17
  lastEmittedStatus = null;
18
+ // Dedup: suppress near-identical messages within a short window
19
+ recentMessages = [];
20
+ static DEDUP_WINDOW_MS = 3e4;
21
+ static DEDUP_SIMILARITY_THRESHOLD = 0.7;
18
22
  // Early-buffering: events that arrive before callbacks are registered
19
23
  earlyMessages = [];
20
24
  earlyStop = false;
@@ -237,6 +241,7 @@ var AgentConnection = class {
237
241
  // ── Convenience methods (thin wrappers around call / emit) ─────────
238
242
  emitStatus(status) {
239
243
  this.lastEmittedStatus = status;
244
+ this.flushEvents();
240
245
  void this.call("reportAgentStatus", {
241
246
  sessionId: this.config.sessionId,
242
247
  status
@@ -245,12 +250,42 @@ var AgentConnection = class {
245
250
  }
246
251
  postChatMessage(content) {
247
252
  if (!this.socket) return;
253
+ if (this.isDuplicateMessage(content)) {
254
+ process.stderr.write(`[dedup] Suppressed near-duplicate message
255
+ `);
256
+ return;
257
+ }
248
258
  void this.call("postAgentMessage", {
249
259
  sessionId: this.config.sessionId,
250
260
  content
251
261
  }).catch(() => {
252
262
  });
253
263
  }
264
+ isDuplicateMessage(content) {
265
+ const now = Date.now();
266
+ this.recentMessages = this.recentMessages.filter(
267
+ (m) => now - m.timestamp < _AgentConnection.DEDUP_WINDOW_MS
268
+ );
269
+ const words = new Set(
270
+ content.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length >= 3)
271
+ );
272
+ if (words.size === 0) return false;
273
+ for (const recent of this.recentMessages) {
274
+ let intersection = 0;
275
+ for (const w of words) {
276
+ if (recent.words.has(w)) intersection++;
277
+ }
278
+ const union = (/* @__PURE__ */ new Set([...words, ...recent.words])).size;
279
+ if (union > 0 && intersection / union > _AgentConnection.DEDUP_SIMILARITY_THRESHOLD) {
280
+ return true;
281
+ }
282
+ }
283
+ this.recentMessages.push({ words, timestamp: now });
284
+ if (this.recentMessages.length > 3) {
285
+ this.recentMessages.shift();
286
+ }
287
+ return false;
288
+ }
254
289
  sendHeartbeat() {
255
290
  if (!this.socket) return;
256
291
  const statusMap = {
@@ -696,10 +731,12 @@ function tryPush(cwd, branch) {
696
731
  }
697
732
  function isAuthError(cwd) {
698
733
  try {
699
- execSync("git push --dry-run 2>&1", { cwd, stdio: ["ignore", "pipe", "pipe"] });
734
+ execSync("git push --dry-run", { cwd, stdio: ["ignore", "pipe", "pipe"] });
700
735
  return false;
701
736
  } catch (err) {
702
- const msg = err instanceof Error ? err.stderr?.toString() ?? err.message : "";
737
+ const stderr = err.stderr?.toString() ?? "";
738
+ const stdout = err.stdout?.toString() ?? "";
739
+ const msg = stderr || stdout || (err instanceof Error ? err.message : "");
703
740
  return /authentication|authorization|403|401|token/i.test(msg);
704
741
  }
705
742
  }
@@ -2890,6 +2927,45 @@ function buildPmTools(connection, options) {
2890
2927
  // src/tools/discovery-tools.ts
2891
2928
  import { z as z3 } from "zod";
2892
2929
  var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2930
+ function buildSearchIconsTool(connection) {
2931
+ return defineTool(
2932
+ "search_icons",
2933
+ "Search for icons by keyword. Searches both the Conveyor database (existing icons) and FontAwesome library (~2200 icons). Returns matching icons with IDs and preview info. Use this to find an icon before generating one.",
2934
+ {
2935
+ query: z3.string().describe("Search query for icon name or keyword (e.g. 'bug', 'gear', 'star')")
2936
+ },
2937
+ async ({ query }) => {
2938
+ try {
2939
+ const [dbIcons, faIcons] = await Promise.all([
2940
+ connection.call("listIcons", { sessionId: connection.sessionId }),
2941
+ connection.call("searchFaIcons", { sessionId: connection.sessionId, query })
2942
+ ]);
2943
+ const q = query.toLowerCase();
2944
+ const matchingDbIcons = dbIcons.filter((icon) => icon.name.toLowerCase().includes(q));
2945
+ const dbFaIds = new Set(matchingDbIcons.map((i) => i.name));
2946
+ const uniqueFaIcons = faIcons.filter((fa) => !dbFaIds.has(fa.fontAwesomeId));
2947
+ const results = {
2948
+ existingIcons: matchingDbIcons.map((i) => ({
2949
+ id: i.id,
2950
+ name: i.name,
2951
+ source: "database"
2952
+ })),
2953
+ fontAwesomeIcons: uniqueFaIcons.map((fa) => ({
2954
+ fontAwesomeId: fa.fontAwesomeId,
2955
+ label: fa.label,
2956
+ styles: fa.styles,
2957
+ source: "fontawesome"
2958
+ }))
2959
+ };
2960
+ return textResult(JSON.stringify(results, null, 2));
2961
+ } catch (error) {
2962
+ const message = error instanceof Error ? error.message : "Unknown error";
2963
+ return textResult(`Failed to search icons: ${message}`);
2964
+ }
2965
+ },
2966
+ { annotations: { readOnlyHint: true } }
2967
+ );
2968
+ }
2893
2969
  function buildIconTools(connection) {
2894
2970
  return [
2895
2971
  defineTool(
@@ -2912,10 +2988,10 @@ function buildIconTools(connection) {
2912
2988
  ),
2913
2989
  defineTool(
2914
2990
  "generate_task_icon",
2915
- "Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
2991
+ "Find a FontAwesome icon matching the description and assign it to this task. Falls back to a placeholder if no match is found. Provide a concise keyword or description.",
2916
2992
  {
2917
2993
  prompt: z3.string().describe(
2918
- "Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
2994
+ "Keyword or description to search FontAwesome icons (e.g. 'bug', 'gear', 'rocket')"
2919
2995
  ),
2920
2996
  aspectRatio: z3.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
2921
2997
  },
@@ -2970,6 +3046,7 @@ function buildDiscoveryTools(connection) {
2970
3046
  }
2971
3047
  }
2972
3048
  ),
3049
+ buildSearchIconsTool(connection),
2973
3050
  ...buildIconTools(connection)
2974
3051
  ];
2975
3052
  }
@@ -4880,6 +4957,7 @@ async function handleExitPlanMode(host, input) {
4880
4957
  host.connection.postChatMessage(
4881
4958
  "Plan complete. The task stays in Planning \u2014 identify it or switch to Build mode when ready."
4882
4959
  );
4960
+ host.requestStop();
4883
4961
  return { behavior: "allow", updatedInput: input };
4884
4962
  }
4885
4963
  await host.connection.triggerIdentification();
@@ -5392,6 +5470,10 @@ var QueryBridge = class {
5392
5470
  this.costTracker = new CostTracker();
5393
5471
  this.planSync = new PlanSync(workspaceDir, connection);
5394
5472
  }
5473
+ connection;
5474
+ mode;
5475
+ runnerConfig;
5476
+ callbacks;
5395
5477
  harness;
5396
5478
  costTracker;
5397
5479
  planSync;
@@ -5485,6 +5567,7 @@ var QueryBridge = class {
5485
5567
  return bridge._abortController;
5486
5568
  },
5487
5569
  isStopped: () => bridge._stopped,
5570
+ requestStop: () => bridge.stop(),
5488
5571
  createInputStream: (prompt) => bridge.createInputStream(prompt),
5489
5572
  snapshotPlanFiles: () => bridge.planSync.snapshotPlanFiles(),
5490
5573
  syncPlanFile: () => bridge.planSync.syncPlanFile(),
@@ -5560,8 +5643,12 @@ var SessionRunner = class _SessionRunner {
5560
5643
  return this.stopped;
5561
5644
  }
5562
5645
  // ── Main lifecycle ─────────────────────────────────────────────────
5563
- // oxlint-disable-next-line max-lines-per-function, complexity -- lifecycle orchestration is inherently sequential
5564
- async start() {
5646
+ /**
5647
+ * Establish the API connection, wire callbacks, and join the session room.
5648
+ * Call this before run() when you need to send events (e.g. setup/start
5649
+ * command output) before the main agent lifecycle begins.
5650
+ */
5651
+ async connect() {
5565
5652
  await this.setState("connecting");
5566
5653
  await this.connection.connect();
5567
5654
  await this.setState("connected");
@@ -5576,6 +5663,13 @@ var SessionRunner = class _SessionRunner {
5576
5663
  this.pendingMessages.push({ content: msg.content, userId: msg.userId });
5577
5664
  }
5578
5665
  }
5666
+ }
5667
+ /**
5668
+ * Run the main agent lifecycle: fetch context, resolve mode, execute, loop.
5669
+ * Requires connect() to have been called first.
5670
+ */
5671
+ // oxlint-disable-next-line max-lines-per-function, complexity -- lifecycle orchestration is inherently sequential
5672
+ async run() {
5579
5673
  await this.setState("fetching_context");
5580
5674
  try {
5581
5675
  const ctx = await this.connection.call("getTaskContext", {
@@ -5623,6 +5717,11 @@ var SessionRunner = class _SessionRunner {
5623
5717
  }
5624
5718
  await this.shutdown("finished");
5625
5719
  }
5720
+ /** Convenience wrapper: connect() then run(). */
5721
+ async start() {
5722
+ await this.connect();
5723
+ await this.run();
5724
+ }
5626
5725
  // ── Core loop ──────────────────────────────────────────────────────
5627
5726
  async coreLoop() {
5628
5727
  while (!this.stopped) {
@@ -6170,6 +6269,8 @@ var CommitWatcher = class {
6170
6269
  this.config = config;
6171
6270
  this.callbacks = callbacks;
6172
6271
  }
6272
+ config;
6273
+ callbacks;
6173
6274
  interval = null;
6174
6275
  lastKnownRemoteSha = null;
6175
6276
  branch = null;
@@ -6729,6 +6830,8 @@ function spawnChildAgent(assignment, workDir) {
6729
6830
  const childEnv = { ...process.env };
6730
6831
  delete childEnv.CONVEYOR_PROJECT_TOKEN;
6731
6832
  delete childEnv.CONVEYOR_PROJECT_ID;
6833
+ delete childEnv.CONVEYOR_SETUP_COMMAND;
6834
+ delete childEnv.CONVEYOR_START_COMMAND;
6732
6835
  const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
6733
6836
  const effectiveApiUrl = apiUrl || process.env.CONVEYOR_API_URL || "";
6734
6837
  if (!effectiveApiUrl) {
@@ -7342,4 +7445,4 @@ export {
7342
7445
  loadForwardPorts,
7343
7446
  loadConveyorConfig
7344
7447
  };
7345
- //# sourceMappingURL=chunk-FCRGYU2M.js.map
7448
+ //# sourceMappingURL=chunk-7RSDCWEI.js.map