@calltelemetry/openclaw-linear 0.9.15 → 0.9.17

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
@@ -23,20 +23,25 @@ Connect Linear to AI agents. Issues get triaged, implemented, and audited — au
23
23
  - [x] Linear OAuth app webhook (AgentSessionEvent created/prompted)
24
24
  - [x] Linear API integration (issues, comments, labels, state transitions)
25
25
  - [x] Agent routing (`@mentions`, natural language intent classifier)
26
+ - [x] Intent gate + scope enforcement (all 4 webhook paths, 3-layer defense)
26
27
  - [x] Auto-triage (story points, labels, priority — read-only)
27
28
  - [x] Complexity-tier dispatch (small → Haiku, medium → Sonnet, high → Opus)
28
29
  - [x] Isolated git worktrees per dispatch
29
30
  - [x] Worker → Auditor pipeline (hard-enforced, not LLM-mediated)
30
31
  - [x] Audit rework loop (gaps fed back, automatic retry)
31
32
  - [x] Watchdog timeout + escalation
32
- - [x] Webhook deduplication (60s sliding window across session/comment/assignment)
33
- - [ ] Multi-repo worktree support
34
- - [ ] Project planner (interview → user stories → sub-issues DAG dispatch)
35
- - [ ] Cross-model plan review (Claude Codex Gemini)
33
+ - [x] Webhook deduplication (60s sliding window + `activeRuns` race guard on all paths)
34
+ - [x] Webhook auto-provisioning (`webhooks setup` CLI, `doctor --fix`)
35
+ - [x] Multi-repo worktree support
36
+ - [x] Project planner (interview user stories → sub-issues → DAG dispatch)
37
+ - [x] Cross-model plan review (Claude ↔ Codex ↔ Gemini)
36
38
  - [x] Issue closure with summary report
37
- - [ ] Sub-issue decomposition (orchestrator-level only)
39
+ - [x] Sub-issue decomposition (orchestrator-level only)
38
40
  - [x] `spawn_agent` / `ask_agent` sub-agent tools
39
- - [x] CI + coverage badges (1000+ tests, Codecov integration)
41
+ - [x] CI + coverage badges (1170+ tests, Codecov integration)
42
+ - [x] Setup wizard (`openclaw openclaw-linear setup`) + `doctor --fix` auto-repair
43
+ - [x] Project context auto-detection (repo, framework, build/test commands → worker/audit prompts)
44
+ - [x] Per-backend CLI tools (`cli_codex`, `cli_claude`, `cli_gemini`) with Linear session activity streaming
40
45
  - [ ] **Worktree → PR merge** — `createPullRequest()` exists but is not wired into the pipeline. After audit pass, commits sit on a `codex/{identifier}` branch. You create the PR manually.
41
46
  - [ ] **Sub-agent worktree sharing** — Sub-agents spawned via `spawn_agent`/`ask_agent` do not inherit the parent worktree. They run in their own session without code access.
42
47
  - [ ] **Parallel worktree conflict resolution** — DAG dispatch runs up to 3 issues concurrently in separate worktrees, but there's no merge conflict detection across them.
@@ -107,12 +112,13 @@ The end result: you work in Linear. You create issues, assign them, comment in p
107
112
  ### Multi-Agent & Routing
108
113
 
109
114
  - **Named agents** — Define agents with different roles and expertise. Route work by `@mention` or natural language ("hey kaylee look at this").
110
- - **Intent classification** — An LLM classifier (~300 tokens, ~2s) understands what you want from any comment. Regex fallback if the classifier fails.
115
+ - **Intent classification** — An LLM classifier (~300 tokens, ~2s) runs on every user request across all webhook paths — not just comments. Classifies intent and gates work requests on untriaged issues. Regex fallback if the classifier fails.
116
+ - **Scope enforcement** — Three-layer defense prevents agents from building code on issues that haven't been planned. Intent gate blocks `request_work` pre-dispatch; prompt-level constraints limit CLI tools to planning-only; a `before_tool_call` hook prepends hard constraints to worker prompts.
111
117
  - **One-time detour** — `@mention` a different agent in a session and it handles that single interaction. The session stays with the original agent.
112
118
 
113
119
  ### Multi-Backend & Multi-Repo
114
120
 
115
- - **Three coding backends** — Codex (OpenAI), Claude (Anthropic), Gemini (Google). Configurable globally or per-agent. The agent writes the prompt; the plugin handles backend selection.
121
+ - **Three coding backends** — Codex (OpenAI), Claude (Anthropic), Gemini (Google). Configurable globally or per-agent. Each backend registers as a dedicated tool (`cli_codex`, `cli_claude`, `cli_gemini`) so agents and Linear session UI show exactly which backend is running. Per-agent overrides let you assign different backends to different team members.
116
122
  - **Multi-repo dispatch** — Tag an issue with `<!-- repos: api, frontend -->` and the worker gets isolated worktrees for each repo. One issue, multiple codebases, one agent session.
117
123
 
118
124
  ### Operations
@@ -698,24 +704,53 @@ flowchart LR
698
704
  | "hey kaylee can you look at this?" | Routes to Kaylee (no `@` needed) |
699
705
  | "@mal close this issue" | Routes to Mal (one-time detour) and closes the issue |
700
706
  | "what can I do here?" | Default agent responds (not silently dropped) |
701
- | "fix the search bug" | Default agent dispatches work |
707
+ | "fix the search bug" | Default agent dispatches work (if issue is In Progress) |
708
+ | "build me a stock trading app" (on Backlog issue) | Blocked — agent explains the issue needs scoping first |
702
709
  | "close this" / "mark as done" / "this is resolved" | Generates closure report, transitions issue to completed |
703
710
 
704
- `@mentions` still work as a fast pathif you write `@kaylee`, the classifier is skipped entirely for speed.
711
+ Intent classification runs on **all webhook paths**not just `Comment.create`. `AgentSessionEvent.created`, `AgentSessionEvent.prompted`, and `@mention` fast paths all classify intent and enforce scope.
705
712
 
706
713
  > **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.
707
714
 
715
+ ### Scope Enforcement — Work Request Gate
716
+
717
+ When a user's intent is classified as `request_work`, the plugin checks the issue's workflow state before dispatching. Issues that haven't reached **In Progress** get a polite rejection instead of an agent run:
718
+
719
+ ```
720
+ This issue (ENG-123) is in Backlog — it needs planning and scoping before implementation.
721
+
722
+ To move forward:
723
+ 1. Update the issue description with requirements and acceptance criteria
724
+ 2. Move the issue to In Progress
725
+ 3. Then ask me to implement it
726
+
727
+ I can help you scope and plan — just ask questions or discuss the approach.
728
+ ```
729
+
730
+ This prevents the most common failure mode: an agent receiving a casual comment like "build X" on an unscoped issue and immediately spinning up a CLI tool to build something that was never planned.
731
+
732
+ **What's allowed on untriaged issues:**
733
+ - Questions, discussion, scope refinement
734
+ - Planning (`plan_start`, `plan_continue`, `plan_finalize`)
735
+ - Agent routing (`@mention` or natural language)
736
+ - Issue closure
737
+ - `cli_codex`/`cli_claude`/`cli_gemini` in **planning mode only** — workers can explore code and write plan files but cannot create, modify, or delete source code
738
+
739
+ **What's blocked on untriaged issues:**
740
+ - `request_work` intent — the gate returns a rejection message before any agent runs
741
+ - Full CLI tool implementation — even if the orchestrator LLM ignores prompt rules, a `before_tool_call` hook prepends hard planning-only constraints to the worker's prompt
742
+
708
743
  ### Agent Routing
709
744
 
710
745
  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:
711
746
 
712
- | Webhook Path | How agent is selected |
713
- |---|---|
714
- | `Comment.create` | `@mention` in comment text specific agent. No mention → intent classifier may detect agent name ("hey kaylee") → `ask_agent` intent. Otherwise → default agent. |
715
- | `AgentSessionEvent.created` | Scans user's message for `@mention` aliases → routes to mentioned agent for that interaction. No mention → default agent. |
716
- | `AgentSessionEvent.prompted` | Same as `created` — scans follow-up message for `@mention` → one-time detour to mentioned agent. No mention → default agent. |
717
- | `Issue.update` (assignment) | Always dispatches to default agent. |
718
- | `Issue.create` (triage) | Always dispatches to default agent. |
747
+ | Webhook Path | How agent is selected | Scope gate |
748
+ |---|---|---|
749
+ | `Comment.create` | `@mention` specific agent (with intent gate). No mention → intent classifier may detect agent name ("hey kaylee") → `ask_agent` intent. Otherwise → default agent. | `request_work` blocked on untriaged |
750
+ | `AgentSessionEvent.created` | Scans user's message for `@mention` aliases → routes to mentioned agent. No mention → default agent. | `request_work` blocked on untriaged |
751
+ | `AgentSessionEvent.prompted` | Same as `created` — scans follow-up message for `@mention` → one-time detour. No mention → default agent. | `request_work` blocked on untriaged |
752
+ | `Issue.update` (assignment) | Always dispatches to default agent. | Full dispatch pipeline |
753
+ | `Issue.create` (triage) | Always dispatches to default agent. | Read-only triage |
719
754
 
720
755
  **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.
721
756
 
@@ -854,7 +889,7 @@ Add settings under the plugin entry in `openclaw.json`:
854
889
  | `notifications` | object | — | Notification targets (see [Notifications](#notifications)) |
855
890
  | `inactivitySec` | number | `120` | Kill agent if silent this long |
856
891
  | `maxTotalSec` | number | `7200` | Max total agent session time |
857
- | `toolTimeoutSec` | number | `600` | Max single `code_run` time |
892
+ | `toolTimeoutSec` | number | `600` | Max single CLI tool time |
858
893
  | `enableGuidance` | boolean | `true` | Inject Linear workspace/team guidance into agent prompts |
859
894
  | `teamGuidanceOverrides` | object | — | Per-team guidance toggle. Key = team ID, value = boolean. Unset teams inherit `enableGuidance`. |
860
895
  | `claudeApiKey` | string | — | Anthropic API key for Claude CLI (passed as `ANTHROPIC_API_KEY` env var). Required if using Claude backend. |
@@ -921,7 +956,7 @@ Create `coding-tools.json` in the plugin root to configure which CLI backend age
921
956
  }
922
957
  ```
923
958
 
924
- The agent calls `code_run` without knowing which backend is active. Resolution order: explicit `backend` parameter > per-agent override > global default > `"codex"`.
959
+ Each backend registers as a dedicated tool — `cli_codex`, `cli_claude`, or `cli_gemini` — so agents and Linear's session UI show exactly which backend is running. The agent's prompt references the correct tool name automatically. Resolution order for which tool is exposed: per-agent override (`agentCodingTools`) > global default (`codingTool`) > `"codex"`.
925
960
 
926
961
  #### Claude API Key
927
962
 
@@ -1095,7 +1130,7 @@ rework:
1095
1130
  | `{{reviewModel}}` | Name of cross-model reviewer (planner review) |
1096
1131
  | `{{crossModelFeedback}}` | Review recommendations (planner review) |
1097
1132
  | `{{guidance}}` | Linear workspace/team guidance (if available, empty string otherwise) |
1098
- | `{{projectContext}}` | Project context from config (project name, repo paths). Framework/build/test info belongs in CLAUDE.md. |
1133
+ | `{{projectContext}}` | Auto-detected project context (repo paths, framework, build commands, test commands) injected into worker and audit prompts. |
1099
1134
 
1100
1135
  ### CLI
1101
1136
 
@@ -1256,7 +1291,7 @@ If an agent goes silent (LLM timeout, API hang, CLI lockup), the watchdog handle
1256
1291
  |---|---|---|
1257
1292
  | `inactivitySec` | 120s | Kill if no output for this long |
1258
1293
  | `maxTotalSec` | 7200s (2 hrs) | Hard ceiling on total session time |
1259
- | `toolTimeoutSec` | 600s (10 min) | Max time for a single `code_run` call |
1294
+ | `toolTimeoutSec` | 600s (10 min) | Max time for a single CLI tool call |
1260
1295
 
1261
1296
  Configure per-agent in `agent-profiles.json` or globally in plugin config.
1262
1297
 
@@ -1266,9 +1301,9 @@ Configure per-agent in `agent-profiles.json` or globally in plugin config.
1266
1301
 
1267
1302
  Every agent session gets these registered tools. They're available as native tool calls — no CLI parsing, no shell execution, no flag guessing.
1268
1303
 
1269
- ### `code_run` — Coding backend dispatch
1304
+ ### `cli_codex` / `cli_claude` / `cli_gemini` — Coding backend tools
1270
1305
 
1271
- Sends a task to whichever coding CLI is configured (Codex, Claude Code, or Gemini). The agent writes the prompt; the plugin handles backend selection, worktree setup, and output capture.
1306
+ Three per-backend tools that send tasks to their respective coding CLIs. Each agent sees only the tool matching its configured backend (e.g., an agent configured for `codex` gets `cli_codex`). The tool name is visible in Linear's agent session UI, so you always know which backend is running. The agent writes the prompt; the plugin handles worktree setup, session activity streaming, and output capture.
1272
1307
 
1273
1308
  ### `linear_issues` — Native Linear API
1274
1309
 
@@ -1289,7 +1324,7 @@ Agents call `linear_issues` with typed JSON parameters. The tool wraps the Linea
1289
1324
 
1290
1325
  Delegate work to other crew agents. `spawn_agent` is fire-and-forget (parallel), `ask_agent` waits for a reply (synchronous). Disabled with `enableOrchestration: false`.
1291
1326
 
1292
- Sub-agents run in their own context — they do **not** share the parent's worktree or get `code_run` access. They're useful for reasoning, research, and coordination (e.g., "ask Inara how to phrase this error message") but cannot directly modify code. To give a sub-agent code context, include the relevant snippets in the task message.
1327
+ Sub-agents run in their own context — they do **not** share the parent's worktree or get CLI tool access. They're useful for reasoning, research, and coordination (e.g., "ask Inara how to phrase this error message") but cannot directly modify code. To give a sub-agent code context, include the relevant snippets in the task message.
1293
1328
 
1294
1329
  ### `dispatch_history` — Recent dispatch context
1295
1330
 
@@ -1299,10 +1334,10 @@ Returns recent dispatch activity. Agents use this for situational awareness when
1299
1334
 
1300
1335
  Tool access varies by context. Orchestrators get the full toolset; workers and auditors are restricted:
1301
1336
 
1302
- | Context | `linear_issues` | `code_run` | `spawn_agent` / `ask_agent` | Filesystem |
1337
+ | Context | `linear_issues` | `cli_*` | `spawn_agent` / `ask_agent` | Filesystem |
1303
1338
  |---|---|---|---|---|
1304
- | Orchestrator (triaged issue) | Full (read, create, update, comment) | Yes | Yes | Read + write |
1305
- | Orchestrator (untriaged issue) | Read only | Yes | Yes | Read + write |
1339
+ | Orchestrator (triaged issue) | Full (read, create, update, comment) | Yes (backend-specific tool) | Yes | Read + write |
1340
+ | Orchestrator (untriaged issue) | Read only | Planning only | Yes | Read + write |
1306
1341
  | Worker | None | None | None | Read + write |
1307
1342
  | Auditor | Prompt-constrained (has tool, instructed to verify only) | None | None | Read only (by prompt) |
1308
1343
  | Sub-agent (spawn/ask) | None | None | Yes (can chain) | Inherited from parent |
@@ -1311,7 +1346,7 @@ Tool access varies by context. Orchestrators get the full toolset; workers and a
1311
1346
 
1312
1347
  **Auditors** have access to `linear_issues` (the tool is registered) but are instructed via prompt to verify only — they return a JSON verdict, not code or issue mutations. Write access is not enforced at the tool level.
1313
1348
 
1314
- **Sub-agents** spawned via `spawn_agent`/`ask_agent` run in their own session with no worktree access and no `code_run`. They're information workers — useful for reasoning and coordination, not code execution.
1349
+ **Sub-agents** spawned via `spawn_agent`/`ask_agent` run in their own session with no worktree access and no CLI tools. They're information workers — useful for reasoning and coordination, not code execution.
1315
1350
 
1316
1351
  ---
1317
1352
 
@@ -1372,9 +1407,9 @@ The handler dispatches by `type + action`:
1372
1407
  ```mermaid
1373
1408
  flowchart TD
1374
1409
  A["POST /linear/webhook"] --> B{"Event Type"}
1375
- B --> C["AgentSessionEvent.created<br/>→ dedup → scan @mentions → run agent"]
1376
- B --> D["AgentSessionEvent.prompted<br/>→ dedup → scan @mentions → resume agent"]
1377
- B --> E["Comment.create<br/>→ filter self → dedup → intent classify → route"]
1410
+ B --> C["AgentSessionEvent.created<br/>→ dedup → scan @mentions → intent classify → scope gate → run agent"]
1411
+ B --> D["AgentSessionEvent.prompted<br/>→ dedup → scan @mentions → intent classify → scope gate → resume agent"]
1412
+ B --> E["Comment.create<br/>→ filter self → dedup → intent classify → scope gate → route"]
1378
1413
  B --> F["Issue.update<br/>→ check assignment → dispatch"]
1379
1414
  B --> G["Issue.create<br/>→ triage (estimate, labels, priority)"]
1380
1415
  B --> H["AppUserNotification<br/>→ discarded (duplicates workspace events)"]
@@ -1382,26 +1417,39 @@ flowchart TD
1382
1417
 
1383
1418
  ### Intent Classification
1384
1419
 
1385
- When a `Comment.create` event arrives, the plugin classifies the user's intent using a two-tier system:
1420
+ Every user request across all 4 webhook dispatch paths is classified through a two-tier intent system before the agent runs:
1386
1421
 
1387
- 1. **LLM classifier** (~300 tokens, ~2-5s) — a small/fast model parses the comment and returns structured JSON with intent + reasoning
1422
+ 1. **LLM classifier** (~300 tokens, ~2-5s) — a small/fast model parses the message and returns structured JSON with intent + reasoning
1388
1423
  2. **Regex fallback** — if the LLM call fails or times out, static patterns catch common cases
1389
1424
 
1390
- | Intent | Trigger | Handler |
1391
- |---|---|---|
1392
- | `plan_start` | "let's plan the features" | Start planner interview session |
1393
- | `plan_finalize` | "looks good, ship it" | Run plan audit + cross-model review |
1394
- | `plan_abandon` | "cancel planning" | End planning session |
1395
- | `plan_continue` | Any message during active planning | Continue planner conversation |
1396
- | `ask_agent` | "@kaylee" or "hey kaylee" | Route to specific agent by name |
1397
- | `request_work` | "fix the search bug" | Dispatch to default agent |
1398
- | `question` | "what's the status?" | Agent answers without code changes |
1399
- | `close_issue` | "close this" / "mark as done" | Generate closure report + transition state |
1400
- | `general` | Noise, automated messages | Silently dropped |
1425
+ **Where it runs:**
1426
+
1427
+ | Webhook Path | Classification Point |
1428
+ |---|---|
1429
+ | `Comment.create` (no @mention) | Before routing by intent |
1430
+ | `Comment.create` (@mention fast path) | After resolving agent, before dispatch |
1431
+ | `AgentSessionEvent.created` | After enriching issue, before building prompt |
1432
+ | `AgentSessionEvent.prompted` | After enriching issue, before building follow-up prompt |
1433
+
1434
+ **Intents:**
1435
+
1436
+ | Intent | Trigger | Handler | Blocked on untriaged? |
1437
+ |---|---|---|---|
1438
+ | `plan_start` | "let's plan the features" | Start planner interview session | No |
1439
+ | `plan_finalize` | "looks good, ship it" | Run plan audit + cross-model review | No |
1440
+ | `plan_abandon` | "cancel planning" | End planning session | No |
1441
+ | `plan_continue` | Any message during active planning | Continue planner conversation | No |
1442
+ | `ask_agent` | "@kaylee" or "hey kaylee" | Route to specific agent by name | No |
1443
+ | `request_work` | "fix the search bug" | Dispatch to default agent | **Yes** |
1444
+ | `question` | "what's the status?" | Agent answers without code changes | No |
1445
+ | `close_issue` | "close this" / "mark as done" | Generate closure report + transition state | No |
1446
+ | `general` | Noise, automated messages | Silently dropped | No |
1447
+
1448
+ The `request_work` intent is the only one gated by issue state. When the issue is not in **In Progress** (i.e., `stateType !== "started"`), the gate returns a rejection message explaining what the user needs to do. All other intents are allowed regardless of issue state — you can always plan, ask questions, discuss scope, and close issues.
1401
1449
 
1402
1450
  ### Hook Lifecycle
1403
1451
 
1404
- The plugin registers three lifecycle hooks via `api.on()` in `index.ts`:
1452
+ The plugin registers four lifecycle hooks via `api.on()` in `index.ts`:
1405
1453
 
1406
1454
  **`agent_end`** — Dispatch pipeline state machine. When a sub-agent (worker or auditor) finishes:
1407
1455
  - Looks up the session key in dispatch state to find the active dispatch
@@ -1413,6 +1461,13 @@ The plugin registers three lifecycle hooks via `api.on()` in `index.ts`:
1413
1461
  - Reads dispatch state and finds up to 3 active dispatches
1414
1462
  - Prepends a `<dispatch-history>` block so the agent has situational awareness of concurrent work
1415
1463
 
1464
+ **`before_tool_call`** — Planning-only enforcement for CLI tools (`cli_codex`, `cli_claude`, `cli_gemini`). When the active issue is not in "started" state:
1465
+ - Fetches the issue's current workflow state from the Linear API
1466
+ - If the issue is in Triage, Todo, Backlog, or any non-started state, prepends hard constraints to the worker's prompt:
1467
+ - Workers may read/explore files and write plan files (PLAN.md, design docs)
1468
+ - Workers must NOT create/modify/delete source code, run deployments, or make system changes
1469
+ - This is the deepest layer of scope enforcement — even if the orchestrator LLM ignores prompt-level scope rules and calls a CLI tool anyway, the worker receives constraints that prevent implementation
1470
+
1416
1471
  **`message_sending`** — Narration guard. Catches short (~250 char) "Let me explore..." responses where the agent narrates intent without actually calling tools:
1417
1472
  - Appends a warning: "Agent acknowledged but may not have completed the task"
1418
1473
  - Prevents users from thinking the agent did something when it only said it would
@@ -1711,7 +1766,8 @@ openclaw openclaw-linear prompts show # View the active prompts
1711
1766
  ## CLI Reference
1712
1767
 
1713
1768
  ```bash
1714
- # Auth & status
1769
+ # Setup & auth
1770
+ openclaw openclaw-linear setup # Guided first-time setup (profiles, auth, webhook, doctor)
1715
1771
  openclaw openclaw-linear auth # Run OAuth flow
1716
1772
  openclaw openclaw-linear status # Check connection
1717
1773
 
@@ -1775,8 +1831,8 @@ journalctl --user -u openclaw-gateway -f # Watch live logs
1775
1831
  |---|---|
1776
1832
  | Agent goes silent | Watchdog auto-kills after `inactivitySec` and retries. Check logs for `Watchdog KILL`. |
1777
1833
  | Dispatch stuck after watchdog | Both retries failed. Check `.claw/log.jsonl`. Re-assign issue to restart. |
1778
- | `code_run` uses wrong backend | Check `coding-tools.json` — explicit backend > per-agent > global default. Run `code-run doctor` to see routing. |
1779
- | `code_run` fails at runtime | Run `openclaw openclaw-linear code-run doctor` — checks binary, API key, and live callability for each backend. |
1834
+ | `cli_*` uses wrong backend | Check `coding-tools.json` — per-agent override > global default. Run `code-run doctor` to see routing. |
1835
+ | `cli_*` fails at runtime | Run `openclaw openclaw-linear code-run doctor` — checks binary, API key, and live callability for each backend. |
1780
1836
  | Webhook events not arriving | Run `openclaw openclaw-linear webhooks setup` to auto-provision. Both webhooks must point to `/linear/webhook`. Check tunnel is running. |
1781
1837
  | Tunnel down / webhooks silently failing | `systemctl status cloudflared` (or `systemctl --user status cloudflared`). Restart with `systemctl restart cloudflared`. Test: `curl -s -X POST https://your-domain.com/linear/webhook -H 'Content-Type: application/json' -d '{"type":"test"}'` — should return `"ok"`. |
1782
1838
  | OAuth token expired | Auto-refreshes. If stuck, re-run `openclaw openclaw-linear auth` and restart. |
package/index.ts CHANGED
@@ -18,6 +18,7 @@ import { createPlannerTools } from "./src/tools/planner-tools.js";
18
18
  import { registerDispatchCommands } from "./src/infra/commands.js";
19
19
  import { createDispatchHistoryTool } from "./src/tools/dispatch-history-tool.js";
20
20
  import { readDispatchState as readStateForHook, listActiveDispatches as listActiveForHook } from "./src/pipeline/dispatch-state.js";
21
+ import { startTokenRefreshTimer, stopTokenRefreshTimer } from "./src/infra/token-refresh-timer.js";
21
22
 
22
23
  export default function register(api: OpenClawPluginApi) {
23
24
  const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
@@ -337,4 +338,10 @@ export default function register(api: OpenClawPluginApi) {
337
338
  api.logger.info(
338
339
  `Linear agent extension registered (agent: ${agentId}, token: ${tokenInfo.source !== "none" ? `${tokenInfo.source}` : "missing"}, ${cliSummary}, orchestration: ${orchestration})`,
339
340
  );
341
+
342
+ // Start proactive token refresh timer (runs immediately, then every 6h)
343
+ startTokenRefreshTimer(api, pluginConfig);
344
+
345
+ // Clean up timer on process exit
346
+ process.on("beforeExit", () => stopTokenRefreshTimer());
340
347
  }
@@ -17,7 +17,46 @@
17
17
  "codexBaseRepo": { "type": "string", "description": "Path to git repo for Codex worktrees", "default": "/home/claw/ai-workspace" },
18
18
  "enableOrchestration": { "type": "boolean", "description": "Allow agents to spawn sub-agents via spawn_agent/ask_agent tools", "default": true },
19
19
  "worktreeBaseDir": { "type": "string", "description": "Base directory for persistent git worktrees (default: ~/.openclaw/worktrees)" },
20
- "repos": { "type": "object", "description": "Multi-repo map (name → path, e.g. {\"api\": \"/home/claw/api\", \"frontend\": \"/home/claw/frontend\"})", "additionalProperties": { "type": "string" } },
20
+ "repos": {
21
+ "type": "object",
22
+ "description": "Multi-repo map — each key is a repo name, value is a path string or object with path + GitHub identity",
23
+ "additionalProperties": {
24
+ "oneOf": [
25
+ { "type": "string", "description": "Filesystem path (backward compat)" },
26
+ {
27
+ "type": "object",
28
+ "required": ["path"],
29
+ "properties": {
30
+ "path": { "type": "string", "description": "Absolute filesystem path to git repo" },
31
+ "github": { "type": "string", "description": "GitHub owner/repo (e.g. 'calltelemetry/cisco-cdr')" },
32
+ "hostname": { "type": "string", "description": "Git host (default: 'github.com')", "default": "github.com" }
33
+ }
34
+ }
35
+ ]
36
+ }
37
+ },
38
+ "teamMappings": {
39
+ "type": "object",
40
+ "description": "Map Linear team keys to repos, agents, and team-specific context",
41
+ "additionalProperties": {
42
+ "type": "object",
43
+ "properties": {
44
+ "repos": {
45
+ "type": "array",
46
+ "items": { "type": "string" },
47
+ "description": "Repo names (keys in 'repos' config) for this team"
48
+ },
49
+ "defaultAgent": {
50
+ "type": "string",
51
+ "description": "OpenClaw agent ID to handle issues from this team"
52
+ },
53
+ "context": {
54
+ "type": "string",
55
+ "description": "Extra context injected into worker/audit prompts for this team"
56
+ }
57
+ }
58
+ }
59
+ },
21
60
  "dispatchStatePath": { "type": "string", "description": "Path to dispatch state JSON file (default: ~/.openclaw/linear-dispatch-state.json)" },
22
61
  "planningStatePath": { "type": "string", "description": "Path to planning state JSON file (default: ~/.openclaw/linear-planning-state.json)" },
23
62
  "notifications": {
@@ -61,7 +100,10 @@
61
100
  "toolTimeoutSec": { "type": "number", "description": "Max runtime for a single code_run CLI invocation in seconds (default: 600)", "default": 600 },
62
101
  "claudeApiKey": { "type": "string", "description": "Anthropic API key for Claude CLI backend (passed as ANTHROPIC_API_KEY env var)", "sensitive": true },
63
102
  "enableGuidance": { "type": "boolean", "description": "Inject Linear workspace/team guidance into agent prompts (default: true)", "default": true },
64
- "teamGuidanceOverrides": { "type": "object", "description": "Per-team guidance toggle. Key = Linear team ID, value = boolean. Unset teams inherit enableGuidance.", "additionalProperties": { "type": "boolean" } }
103
+ "teamGuidanceOverrides": { "type": "object", "description": "Per-team guidance toggle. Key = Linear team ID, value = boolean. Unset teams inherit enableGuidance.", "additionalProperties": { "type": "boolean" } },
104
+ "enableTmux": { "type": "boolean", "default": true, "description": "Enable tmux wrapping for code_run CLI backends (enables steering)" },
105
+ "enableRetro": { "type": "boolean", "default": true, "description": "Enable post-task retrospective analysis" },
106
+ "retroDir": { "type": "string", "description": "Override path for coding retrospective files (default: {stateDir}/shared/coding)" }
65
107
  }
66
108
  }
67
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calltelemetry/openclaw-linear",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
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",
package/prompts.yaml CHANGED
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Template variables: {{identifier}}, {{title}}, {{description}},
4
4
  # {{worktreePath}}, {{gaps}}, {{tier}}, {{attempt}}, {{guidance}},
5
- # {{projectContext}}
5
+ # {{projectContext}}, {{teamContext}}
6
6
  #
7
7
  # Edit these to customize worker/audit behavior without rebuilding the plugin.
8
8
  # Override path via `promptsPath` in plugin config.
@@ -27,6 +27,7 @@ worker:
27
27
 
28
28
  Worktree: {{worktreePath}}
29
29
  {{projectContext}}
30
+ {{teamContext}}
30
31
 
31
32
  Before writing any code, read these files in the worktree root (if they exist):
32
33
  - CLAUDE.md — project conventions, tech stack, build/test commands, architecture
@@ -70,6 +71,7 @@ audit:
70
71
 
71
72
  Worktree: {{worktreePath}}
72
73
  {{projectContext}}
74
+ {{teamContext}}
73
75
 
74
76
  Before auditing, read these files in the worktree root (if they exist):
75
77
  - CLAUDE.md — project conventions, tech stack, build/test commands
@@ -2,7 +2,7 @@
2
2
  * Recorded API responses from sub-issue decomposition smoke test.
3
3
  * Auto-generated — do not edit manually.
4
4
  * Re-generate by running: npx vitest run src/__test__/smoke-linear-api.test.ts
5
- * Last recorded: 2026-02-22T03:40:54.418Z
5
+ * Last recorded: 2026-02-24T05:15:32.100Z
6
6
  */
7
7
 
8
8
  export const RECORDED = {
@@ -44,20 +44,20 @@ export const RECORDED = {
44
44
  }
45
45
  ],
46
46
  "createParent": {
47
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
48
- "identifier": "UAT-438"
47
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
48
+ "identifier": "UAT-638"
49
49
  },
50
50
  "createSubIssue1": {
51
- "id": "4caa7593-5a51-4795-b98c-29e532589dfe",
52
- "identifier": "UAT-439"
51
+ "id": "a1702cad-3206-4a2b-bb0c-c6ad47df8983",
52
+ "identifier": "UAT-639"
53
53
  },
54
54
  "createSubIssue2": {
55
- "id": "0519065b-95eb-4752-966b-2099bbc5f3d1",
56
- "identifier": "UAT-440"
55
+ "id": "d988eb3a-7d45-4d9b-8ee2-4d7580dd183e",
56
+ "identifier": "UAT-640"
57
57
  },
58
58
  "subIssue1Details": {
59
- "id": "4caa7593-5a51-4795-b98c-29e532589dfe",
60
- "identifier": "UAT-439",
59
+ "id": "a1702cad-3206-4a2b-bb0c-c6ad47df8983",
60
+ "identifier": "UAT-639",
61
61
  "title": "[SMOKE TEST] Sub-Issue 1: Backend API",
62
62
  "description": "Implement the backend search API endpoint.\n\nGiven a search query, when the API is called, then matching results are returned.",
63
63
  "estimate": 2,
@@ -75,6 +75,7 @@ export const RECORDED = {
75
75
  },
76
76
  "team": {
77
77
  "id": "08cba264-d774-4afd-bc93-ee8213d12ef8",
78
+ "key": "UAT",
78
79
  "name": "UAT",
79
80
  "issueEstimationType": "tShirt"
80
81
  },
@@ -83,16 +84,16 @@ export const RECORDED = {
83
84
  },
84
85
  "project": null,
85
86
  "parent": {
86
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
87
- "identifier": "UAT-438"
87
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
88
+ "identifier": "UAT-638"
88
89
  },
89
90
  "relations": {
90
91
  "nodes": []
91
92
  }
92
93
  },
93
94
  "subIssue2Details": {
94
- "id": "0519065b-95eb-4752-966b-2099bbc5f3d1",
95
- "identifier": "UAT-440",
95
+ "id": "d988eb3a-7d45-4d9b-8ee2-4d7580dd183e",
96
+ "identifier": "UAT-640",
96
97
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI",
97
98
  "description": "Build the frontend search UI component.\n\nGiven the search page loads, when the user types a query, then results display in real-time.",
98
99
  "estimate": 3,
@@ -110,6 +111,7 @@ export const RECORDED = {
110
111
  },
111
112
  "team": {
112
113
  "id": "08cba264-d774-4afd-bc93-ee8213d12ef8",
114
+ "key": "UAT",
113
115
  "name": "UAT",
114
116
  "issueEstimationType": "tShirt"
115
117
  },
@@ -118,18 +120,18 @@ export const RECORDED = {
118
120
  },
119
121
  "project": null,
120
122
  "parent": {
121
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
122
- "identifier": "UAT-438"
123
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
124
+ "identifier": "UAT-638"
123
125
  },
124
126
  "relations": {
125
127
  "nodes": []
126
128
  }
127
129
  },
128
130
  "parentDetails": {
129
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
130
- "identifier": "UAT-438",
131
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
132
+ "identifier": "UAT-638",
131
133
  "title": "[SMOKE TEST] Sub-Issue Parent: Search Feature",
132
- "description": "Auto-generated by smoke test to verify sub-issue decomposition.\n\nThis parent issue should have two sub-issues created under it.\n\nCreated: 2026-02-22T03:40:52.229Z",
134
+ "description": "Auto-generated by smoke test to verify sub-issue decomposition.\n\nThis parent issue should have two sub-issues created under it.\n\nCreated: 2026-02-24T05:15:30.285Z",
133
135
  "estimate": null,
134
136
  "state": {
135
137
  "name": "Backlog",
@@ -145,17 +147,12 @@ export const RECORDED = {
145
147
  },
146
148
  "team": {
147
149
  "id": "08cba264-d774-4afd-bc93-ee8213d12ef8",
150
+ "key": "UAT",
148
151
  "name": "UAT",
149
152
  "issueEstimationType": "tShirt"
150
153
  },
151
154
  "comments": {
152
- "nodes": [
153
- {
154
- "body": "This thread is for an agent session with ctclaw.",
155
- "user": null,
156
- "createdAt": "2026-02-22T03:40:53.165Z"
157
- }
158
- ]
155
+ "nodes": []
159
156
  },
160
157
  "project": null,
161
158
  "parent": null,
@@ -164,11 +161,11 @@ export const RECORDED = {
164
161
  }
165
162
  },
166
163
  "createRelation": {
167
- "id": "185dfd6c-362e-48a4-b717-e900407ced84"
164
+ "id": "139541b2-b088-4290-9ded-5b0167a42741"
168
165
  },
169
166
  "subIssue1WithRelation": {
170
- "id": "4caa7593-5a51-4795-b98c-29e532589dfe",
171
- "identifier": "UAT-439",
167
+ "id": "a1702cad-3206-4a2b-bb0c-c6ad47df8983",
168
+ "identifier": "UAT-639",
172
169
  "title": "[SMOKE TEST] Sub-Issue 1: Backend API",
173
170
  "description": "Implement the backend search API endpoint.\n\nGiven a search query, when the API is called, then matching results are returned.",
174
171
  "estimate": 2,
@@ -186,30 +183,25 @@ export const RECORDED = {
186
183
  },
187
184
  "team": {
188
185
  "id": "08cba264-d774-4afd-bc93-ee8213d12ef8",
186
+ "key": "UAT",
189
187
  "name": "UAT",
190
188
  "issueEstimationType": "tShirt"
191
189
  },
192
190
  "comments": {
193
- "nodes": [
194
- {
195
- "body": "This thread is for an agent session with ctclaw.",
196
- "user": null,
197
- "createdAt": "2026-02-22T03:40:53.603Z"
198
- }
199
- ]
191
+ "nodes": []
200
192
  },
201
193
  "project": null,
202
194
  "parent": {
203
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
204
- "identifier": "UAT-438"
195
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
196
+ "identifier": "UAT-638"
205
197
  },
206
198
  "relations": {
207
199
  "nodes": [
208
200
  {
209
201
  "type": "blocks",
210
202
  "relatedIssue": {
211
- "id": "0519065b-95eb-4752-966b-2099bbc5f3d1",
212
- "identifier": "UAT-440",
203
+ "id": "d988eb3a-7d45-4d9b-8ee2-4d7580dd183e",
204
+ "identifier": "UAT-640",
213
205
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI"
214
206
  }
215
207
  }
@@ -217,8 +209,8 @@ export const RECORDED = {
217
209
  }
218
210
  },
219
211
  "subIssue2WithRelation": {
220
- "id": "0519065b-95eb-4752-966b-2099bbc5f3d1",
221
- "identifier": "UAT-440",
212
+ "id": "d988eb3a-7d45-4d9b-8ee2-4d7580dd183e",
213
+ "identifier": "UAT-640",
222
214
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI",
223
215
  "description": "Build the frontend search UI component.\n\nGiven the search page loads, when the user types a query, then results display in real-time.",
224
216
  "estimate": 3,
@@ -236,22 +228,17 @@ export const RECORDED = {
236
228
  },
237
229
  "team": {
238
230
  "id": "08cba264-d774-4afd-bc93-ee8213d12ef8",
231
+ "key": "UAT",
239
232
  "name": "UAT",
240
233
  "issueEstimationType": "tShirt"
241
234
  },
242
235
  "comments": {
243
- "nodes": [
244
- {
245
- "body": "This thread is for an agent session with ctclaw.",
246
- "user": null,
247
- "createdAt": "2026-02-22T03:40:53.840Z"
248
- }
249
- ]
236
+ "nodes": []
250
237
  },
251
238
  "project": null,
252
239
  "parent": {
253
- "id": "933fa3c0-981a-4d9e-8b2c-7c5c4d5b4349",
254
- "identifier": "UAT-438"
240
+ "id": "f236a0f3-a365-4e05-9b84-7c965da87c03",
241
+ "identifier": "UAT-638"
255
242
  },
256
243
  "relations": {
257
244
  "nodes": []
@@ -228,7 +228,7 @@ describe("runAgent subprocess", () => {
228
228
  const noisyOutput = [
229
229
  "[plugins] Dispatch gateway methods registered",
230
230
  "[plugins] Linear agent extension registered (agent: zoe)",
231
- '[plugins] code_run: default backend=codex, aliases={"claude":"claude"}',
231
+ '[plugins] cli tools registered: cli_codex, cli_claude, cli_gemini (agent default: cli_codex)',
232
232
  JSON.stringify({ payloads: [{ text: "clean response" }], meta: {} }),
233
233
  ].join("\n");
234
234
  (api.runtime.system as any).runCommandWithTimeout = vi.fn().mockResolvedValue({