@calltelemetry/openclaw-linear 0.8.6 → 0.8.8

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/README.md CHANGED
@@ -305,6 +305,7 @@ User comment → Intent Classifier (small model, ~2s) → Route to handler
305
305
  | "looks good, ship it" (during planning) | Runs plan audit + cross-model review |
306
306
  | "nevermind, cancel this" (during planning) | Exits planning mode |
307
307
  | "hey kaylee can you look at this?" | Routes to Kaylee (no `@` needed) |
308
+ | "@mal close this issue" | Routes to Mal (one-time detour) and closes the issue |
308
309
  | "what can I do here?" | Default agent responds (not silently dropped) |
309
310
  | "fix the search bug" | Default agent dispatches work |
310
311
  | "close this" / "mark as done" / "this is resolved" | Generates closure report, transitions issue to completed |
@@ -313,6 +314,41 @@ User comment → Intent Classifier (small model, ~2s) → Route to handler
313
314
 
314
315
  > **Tip:** Configure `classifierAgentId` to point to a small/fast model agent (like Haiku) for low-latency, low-cost intent classification. The classifier only needs ~300 tokens per call.
315
316
 
317
+ ### Agent Routing
318
+
319
+ The plugin supports a multi-agent team where one agent is the default (`isDefault: true` in agent profiles) and others are routed to on demand. Routing works across all webhook paths:
320
+
321
+ | Webhook Path | How agent is selected |
322
+ |---|---|
323
+ | `Comment.create` | `@mention` in comment text → specific agent. No mention → intent classifier may detect agent name ("hey kaylee") → `ask_agent` intent. Otherwise → default agent. |
324
+ | `AgentSessionEvent.created` | Scans user's message for `@mention` aliases → routes to mentioned agent for that interaction. No mention → default agent. |
325
+ | `AgentSessionEvent.prompted` | Same as `created` — scans follow-up message for `@mention` → one-time detour to mentioned agent. No mention → default agent. |
326
+ | `Issue.update` (assignment) | Always dispatches to default agent. |
327
+ | `Issue.create` (triage) | Always dispatches to default agent. |
328
+
329
+ **One-time detour:** When you `@mention` an agent in a session that belongs to a different default agent, the mentioned agent handles that single interaction. The session itself stays owned by whoever created it — subsequent messages without `@mentions` go back to the default. This lets you ask a specific agent for help without permanently switching context.
330
+
331
+ **Agent profiles** are configured in `~/.openclaw/agent-profiles.json`:
332
+
333
+ ```json
334
+ {
335
+ "agents": {
336
+ "mal": {
337
+ "label": "Mal",
338
+ "mentionAliases": ["mal"],
339
+ "isDefault": false
340
+ },
341
+ "zoe": {
342
+ "label": "Zoe",
343
+ "mentionAliases": ["zoe"],
344
+ "isDefault": true
345
+ }
346
+ }
347
+ }
348
+ ```
349
+
350
+ Each agent needs a unique set of `mentionAliases`. The `appAliases` field (e.g. `["ctclaw"]`) is separate — those trigger `AgentSessionEvent` from Linear's own `@app` mention system, not the plugin's routing.
351
+
316
352
  ### Deduplication
317
353
 
318
354
  The webhook handler prevents double-processing through a two-tier guard system:
@@ -993,10 +1029,12 @@ The handler dispatches by `type + action`:
993
1029
  Incoming POST /linear/webhook
994
1030
 
995
1031
  ├─ type=AgentSessionEvent, action=created
996
- │ └─ New agent session on issue → dedup → create LinearAgentApi run agent
1032
+ │ └─ New agent session → dedup → scan message for @mentions
1033
+ │ route to mentioned agent (or default) → run agent
997
1034
 
998
1035
  ├─ type=AgentSessionEvent, action=prompted
999
- │ └─ Follow-up message in existing session → dedup → resume agent with context
1036
+ │ └─ Follow-up message → dedup → scan message for @mentions →
1037
+ │ route to mentioned agent (one-time detour, or default) → resume agent
1000
1038
 
1001
1039
  ├─ type=Comment, action=create
1002
1040
  │ └─ Comment on issue → filter self-comments (viewerId) → dedup →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calltelemetry/openclaw-linear",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Linear Agent plugin for OpenClaw — webhook-driven AI pipeline with OAuth, multi-agent routing, and issue triage",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -299,18 +299,34 @@ export async function handleLinearWebhook(
299
299
  return true;
300
300
  }
301
301
 
302
- const agentId = resolveAgentId(api);
303
302
  const previousComments = payload.previousComments ?? [];
304
303
  const guidanceCtx = extractGuidance(payload);
305
304
 
306
- api.logger.info(`AgentSession created: ${session.id} for issue ${issue?.identifier ?? issue?.id} (comments: ${previousComments.length}, guidance: ${guidanceCtx.guidance ? "yes" : "no"})`)
307
-
308
305
  // Extract the user's latest message from previousComments (NOT from guidance)
309
306
  const lastComment = previousComments.length > 0
310
307
  ? previousComments[previousComments.length - 1]
311
308
  : null;
312
309
  const userMessage = lastComment?.body ?? "";
313
310
 
311
+ // Route to the mentioned agent if the user's message contains an @mention.
312
+ // AgentSessionEvent doesn't carry mention routing — we must check manually.
313
+ const profiles = loadAgentProfiles();
314
+ const mentionPattern = buildMentionPattern(profiles);
315
+ let agentId = resolveAgentId(api);
316
+ if (mentionPattern && userMessage) {
317
+ const mentionMatch = userMessage.match(mentionPattern);
318
+ if (mentionMatch) {
319
+ const alias = mentionMatch[1];
320
+ const resolved = resolveAgentFromAlias(alias, profiles);
321
+ if (resolved) {
322
+ api.logger.info(`AgentSession routed to ${resolved.agentId} via @${alias} mention`);
323
+ agentId = resolved.agentId;
324
+ }
325
+ }
326
+ }
327
+
328
+ api.logger.info(`AgentSession created: ${session.id} for issue ${issue?.identifier ?? issue?.id} agent=${agentId} (comments: ${previousComments.length}, guidance: ${guidanceCtx.guidance ? "yes" : "no"})`);
329
+
314
330
  // Fetch full issue details
315
331
  let enrichedIssue: any = issue;
316
332
  try {
@@ -504,9 +520,23 @@ export async function handleLinearWebhook(
504
520
  return true;
505
521
  }
506
522
 
507
- api.logger.info(`AgentSession prompted (follow-up): ${session.id} issue=${issue?.identifier ?? issue?.id} message="${userMessage.slice(0, 80)}..."`);
523
+ // Route to mentioned agent if user's message contains an @mention (one-time detour)
524
+ const promptedProfiles = loadAgentProfiles();
525
+ const promptedMentionPattern = buildMentionPattern(promptedProfiles);
526
+ let agentId = resolveAgentId(api);
527
+ if (promptedMentionPattern && userMessage) {
528
+ const mentionMatch = userMessage.match(promptedMentionPattern);
529
+ if (mentionMatch) {
530
+ const alias = mentionMatch[1];
531
+ const resolved = resolveAgentFromAlias(alias, promptedProfiles);
532
+ if (resolved) {
533
+ api.logger.info(`AgentSession prompted: routed to ${resolved.agentId} via @${alias} mention`);
534
+ agentId = resolved.agentId;
535
+ }
536
+ }
537
+ }
508
538
 
509
- const agentId = resolveAgentId(api);
539
+ api.logger.info(`AgentSession prompted (follow-up): ${session.id} issue=${issue?.identifier ?? issue?.id} agent=${agentId} message="${userMessage.slice(0, 80)}..."`);
510
540
 
511
541
  // Run agent for follow-up (non-blocking)
512
542
  activeRuns.add(issue.id);