@calltelemetry/openclaw-linear 0.9.5 → 0.9.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.
@@ -17,7 +17,7 @@ import { startProjectDispatch } from "./dag-dispatch.js";
17
17
  import { emitDiagnostic } from "../infra/observability.js";
18
18
  import { classifyIntent } from "./intent-classify.js";
19
19
  import { extractGuidance, formatGuidanceAppendix, cacheGuidanceForTeam, getCachedGuidanceForTeam, isGuidanceEnabled, _resetGuidanceCacheForTesting } from "./guidance.js";
20
- import { loadAgentProfiles, buildMentionPattern, resolveAgentFromAlias, _resetProfilesCacheForTesting, type AgentProfile } from "../infra/shared-profiles.js";
20
+ import { loadAgentProfiles, buildMentionPattern, resolveAgentFromAlias, validateProfiles, _resetProfilesCacheForTesting, type AgentProfile } from "../infra/shared-profiles.js";
21
21
 
22
22
  // ── Prompt input sanitization ─────────────────────────────────────
23
23
 
@@ -322,6 +322,21 @@ export async function handleLinearWebhook(
322
322
  return true;
323
323
  }
324
324
 
325
+ // Validate agent profiles before doing any work
326
+ const profilesError = validateProfiles();
327
+ if (profilesError) {
328
+ api.logger.error("Agent profiles validation failed — posting setup error to Linear");
329
+ await linearApi.emitActivity(session.id, {
330
+ type: "error",
331
+ body: profilesError,
332
+ }).catch(() => {});
333
+ // Also try posting as a comment in case emitActivity doesn't render markdown
334
+ try {
335
+ await createCommentWithDedup(linearApi, issue.id, profilesError);
336
+ } catch {}
337
+ return true;
338
+ }
339
+
325
340
  const previousComments = payload.previousComments ?? [];
326
341
  const guidanceCtx = extractGuidance(payload);
327
342
 
@@ -331,24 +346,59 @@ export async function handleLinearWebhook(
331
346
  : null;
332
347
  const userMessage = lastComment?.body ?? "";
333
348
 
349
+ // Also extract the session prompt from promptContext — on initial session
350
+ // creation, previousComments is empty but the user's message lives here.
351
+ const promptContext = typeof payload.promptContext === "string" ? payload.promptContext : "";
352
+
334
353
  // Route to the mentioned agent if the user's message contains an @mention.
335
354
  // AgentSessionEvent doesn't carry mention routing — we must check manually.
355
+ // Check the last comment body, promptContext, AND agentSession prompt for @mentions.
336
356
  const profiles = loadAgentProfiles();
337
357
  const mentionPattern = buildMentionPattern(profiles);
338
358
  let agentId = resolveAgentId(api);
339
- if (mentionPattern && userMessage) {
340
- const mentionMatch = userMessage.match(mentionPattern);
341
- if (mentionMatch) {
342
- const alias = mentionMatch[1];
343
- const resolved = resolveAgentFromAlias(alias, profiles);
344
- if (resolved) {
345
- api.logger.info(`AgentSession routed to ${resolved.agentId} via @${alias} mention`);
346
- agentId = resolved.agentId;
359
+ let mentionOverride = false;
360
+ const sessionPrompt = typeof (payload.agentSession as any)?.prompt === "string"
361
+ ? (payload.agentSession as any).prompt : "";
362
+ const textsToScan = [userMessage, sessionPrompt, promptContext].filter(Boolean);
363
+ for (const text of textsToScan) {
364
+ if (mentionOverride) break;
365
+ if (mentionPattern) {
366
+ mentionPattern.lastIndex = 0;
367
+ const mentionMatch = text.match(mentionPattern);
368
+ if (mentionMatch) {
369
+ const alias = mentionMatch[1];
370
+ const resolved = resolveAgentFromAlias(alias, profiles);
371
+ if (resolved) {
372
+ api.logger.info(`AgentSession routed to ${resolved.agentId} via @${alias} mention in ${text === userMessage ? "comment" : text === sessionPrompt ? "session prompt" : "promptContext"}`);
373
+ agentId = resolved.agentId;
374
+ mentionOverride = true;
375
+ }
376
+ }
377
+ }
378
+ }
379
+ // Also try bare-name matching (e.g. "hey mal" without @) in the user's text
380
+ if (!mentionOverride) {
381
+ for (const text of textsToScan) {
382
+ if (mentionOverride) break;
383
+ const lowerText = text.toLowerCase();
384
+ for (const [id, profile] of Object.entries(profiles)) {
385
+ const allNames = [...profile.mentionAliases, ...(profile.appAliases ?? [])];
386
+ for (const name of allNames) {
387
+ // Match bare name at word boundary (e.g. "hey mal" but not "malware")
388
+ const nameRe = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
389
+ if (nameRe.test(lowerText)) {
390
+ api.logger.info(`AgentSession routed to ${id} via bare name "${name}" in ${text === userMessage ? "comment" : text === sessionPrompt ? "session prompt" : "promptContext"}`);
391
+ agentId = id;
392
+ mentionOverride = true;
393
+ break;
394
+ }
395
+ }
396
+ if (mentionOverride) break;
347
397
  }
348
398
  }
349
399
  }
350
400
  // Session affinity: if no @mention override, prefer the agent that last handled this issue
351
- if (agentId === resolveAgentId(api) && issue?.id) {
401
+ if (!mentionOverride && issue?.id) {
352
402
  const affinityAgent = getIssueAffinity(issue.id);
353
403
  if (affinityAgent) {
354
404
  api.logger.info(`AgentSession routed to ${affinityAgent} via session affinity for ${issue.identifier ?? issue.id}`);
@@ -551,10 +601,22 @@ export async function handleLinearWebhook(
551
601
  return true;
552
602
  }
553
603
 
604
+ // Validate agent profiles before doing any work
605
+ const profilesError = validateProfiles();
606
+ if (profilesError) {
607
+ api.logger.error("Agent profiles validation failed — posting setup error to Linear");
608
+ await linearApi.emitActivity(session.id, {
609
+ type: "error",
610
+ body: profilesError,
611
+ }).catch(() => {});
612
+ return true;
613
+ }
614
+
554
615
  // Route to mentioned agent if user's message contains an @mention (one-time detour)
555
616
  const promptedProfiles = loadAgentProfiles();
556
617
  const promptedMentionPattern = buildMentionPattern(promptedProfiles);
557
618
  let agentId = resolveAgentId(api);
619
+ let mentionOverride = false;
558
620
  if (promptedMentionPattern && userMessage) {
559
621
  const mentionMatch = userMessage.match(promptedMentionPattern);
560
622
  if (mentionMatch) {
@@ -563,11 +625,12 @@ export async function handleLinearWebhook(
563
625
  if (resolved) {
564
626
  api.logger.info(`AgentSession prompted: routed to ${resolved.agentId} via @${alias} mention`);
565
627
  agentId = resolved.agentId;
628
+ mentionOverride = true;
566
629
  }
567
630
  }
568
631
  }
569
632
  // Session affinity: if no @mention override, prefer the agent that last handled this issue
570
- if (agentId === resolveAgentId(api) && issue?.id) {
633
+ if (!mentionOverride && issue?.id) {
571
634
  const affinityAgent = getIssueAffinity(issue.id);
572
635
  if (affinityAgent) {
573
636
  api.logger.info(`AgentSession prompted: routed to ${affinityAgent} via session affinity for ${issue.identifier ?? issue.id}`);
@@ -760,6 +823,16 @@ export async function handleLinearWebhook(
760
823
  return true;
761
824
  }
762
825
 
826
+ // Validate agent profiles before doing any work
827
+ const profilesError = validateProfiles();
828
+ if (profilesError) {
829
+ api.logger.error("Agent profiles validation failed — posting setup error to Linear");
830
+ try {
831
+ await createCommentWithDedup(linearApi, issue.id, profilesError);
832
+ } catch {}
833
+ return true;
834
+ }
835
+
763
836
  // Load agent profiles
764
837
  const profiles = loadAgentProfiles();
765
838
  const agentNames = Object.keys(profiles);
@@ -1046,6 +1119,16 @@ export async function handleLinearWebhook(
1046
1119
  return true;
1047
1120
  }
1048
1121
 
1122
+ // Validate agent profiles
1123
+ const profilesError = validateProfiles();
1124
+ if (profilesError) {
1125
+ api.logger.error("Agent profiles validation failed — cannot triage new issue");
1126
+ try {
1127
+ await createCommentWithDedup(linearApi, issue.id, profilesError);
1128
+ } catch {}
1129
+ return true;
1130
+ }
1131
+
1049
1132
  const agentId = resolveAgentId(api);
1050
1133
 
1051
1134
  // Guard: prevent duplicate runs on same issue (also blocks AgentSessionEvent