@calltelemetry/openclaw-linear 0.8.4 → 0.8.6
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 +318 -2
- package/openclaw.plugin.json +3 -1
- package/package.json +1 -1
- package/prompts.yaml +24 -12
- package/src/__test__/fixtures/linear-responses.ts +2 -1
- package/src/__test__/fixtures/webhook-payloads.ts +9 -2
- package/src/__test__/webhook-scenarios.test.ts +150 -0
- package/src/api/linear-api.ts +2 -0
- package/src/infra/cli.ts +27 -0
- package/src/infra/doctor.test.ts +130 -3
- package/src/infra/doctor.ts +216 -11
- package/src/pipeline/guidance.test.ts +222 -0
- package/src/pipeline/guidance.ts +156 -0
- package/src/pipeline/pipeline.ts +23 -2
- package/src/pipeline/webhook.ts +150 -30
- package/src/tools/linear-issues-tool.test.ts +453 -0
- package/src/tools/linear-issues-tool.ts +338 -0
- package/src/tools/tools.test.ts +36 -7
- package/src/tools/tools.ts +9 -2
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Connect Linear to AI agents. Issues get triaged, implemented, and audited — au
|
|
|
16
16
|
- **Say "let's plan the features"?** A planner interviews you, writes user stories, and builds your full issue hierarchy.
|
|
17
17
|
- **Plan looks good?** A different AI model automatically audits the plan before dispatch.
|
|
18
18
|
- **Agent goes silent?** A watchdog kills it and retries automatically.
|
|
19
|
+
- **Linear guidance?** Workspace and team-level guidance from Linear flows into every agent prompt — triage, dispatch, worker, audit.
|
|
19
20
|
- **Want updates?** Get notified on Discord, Slack, Telegram, or Signal.
|
|
20
21
|
|
|
21
22
|
---
|
|
@@ -495,6 +496,8 @@ Add settings under the plugin entry in `openclaw.json`:
|
|
|
495
496
|
| `inactivitySec` | number | `120` | Kill agent if silent this long |
|
|
496
497
|
| `maxTotalSec` | number | `7200` | Max total agent session time |
|
|
497
498
|
| `toolTimeoutSec` | number | `600` | Max single `code_run` time |
|
|
499
|
+
| `enableGuidance` | boolean | `true` | Inject Linear workspace/team guidance into agent prompts |
|
|
500
|
+
| `teamGuidanceOverrides` | object | — | Per-team guidance toggle. Key = team ID, value = boolean. Unset teams inherit `enableGuidance`. |
|
|
498
501
|
| `claudeApiKey` | string | — | Anthropic API key for Claude CLI (passed as `ANTHROPIC_API_KEY` env var). Required if using Claude backend. |
|
|
499
502
|
|
|
500
503
|
### Environment Variables
|
|
@@ -652,6 +655,37 @@ Prompts merge in this order (later layers override earlier ones):
|
|
|
652
655
|
|
|
653
656
|
Each layer only overrides the specific sections you define. Everything else keeps its default.
|
|
654
657
|
|
|
658
|
+
### Linear Guidance
|
|
659
|
+
|
|
660
|
+
Linear's [agent guidance system](https://linear.app/docs/agents-in-linear) lets admins configure workspace-wide and team-specific instructions for agents. This plugin automatically extracts that guidance and appends it as supplementary instructions to all agent prompts.
|
|
661
|
+
|
|
662
|
+
Guidance is configured in Linear at:
|
|
663
|
+
- **Workspace level:** Settings > Agents > Additional guidance (applies across entire org)
|
|
664
|
+
- **Team level:** Team settings > Agents > Additional guidance (takes priority over workspace guidance)
|
|
665
|
+
|
|
666
|
+
See [Agents in Linear](https://linear.app/docs/agents-in-linear) for full documentation on how guidance works.
|
|
667
|
+
|
|
668
|
+
Guidance flows into:
|
|
669
|
+
- **Orchestrator prompts** — AgentSessionEvent and comment handler paths
|
|
670
|
+
- **Worker prompts** — Appended to the task via `{{guidance}}` template variable
|
|
671
|
+
- **Audit prompts** — Appended to the audit task
|
|
672
|
+
- **Triage and closure prompts** — Appended to the triage and close_issue handlers
|
|
673
|
+
|
|
674
|
+
Guidance is cached per-team (24h TTL) so comment webhooks (which don't carry guidance from Linear) can also benefit.
|
|
675
|
+
|
|
676
|
+
**Disable guidance globally:**
|
|
677
|
+
```json
|
|
678
|
+
{ "enableGuidance": false }
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Disable for a specific team:**
|
|
682
|
+
```json
|
|
683
|
+
{
|
|
684
|
+
"enableGuidance": true,
|
|
685
|
+
"teamGuidanceOverrides": { "team-id-here": false }
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
655
689
|
### Example Custom Prompts
|
|
656
690
|
|
|
657
691
|
```yaml
|
|
@@ -690,6 +724,7 @@ rework:
|
|
|
690
724
|
| `{{planSnapshot}}` | Current plan structure (planner prompts) |
|
|
691
725
|
| `{{reviewModel}}` | Name of cross-model reviewer (planner review) |
|
|
692
726
|
| `{{crossModelFeedback}}` | Review recommendations (planner review) |
|
|
727
|
+
| `{{guidance}}` | Linear workspace/team guidance (if available, empty string otherwise) |
|
|
693
728
|
|
|
694
729
|
### CLI
|
|
695
730
|
|
|
@@ -856,6 +891,245 @@ Configure per-agent in `agent-profiles.json` or globally in plugin config.
|
|
|
856
891
|
|
|
857
892
|
---
|
|
858
893
|
|
|
894
|
+
## Agent Tools
|
|
895
|
+
|
|
896
|
+
Every agent session gets these registered tools. They're available as native tool calls — no CLI parsing, no shell execution, no flag guessing.
|
|
897
|
+
|
|
898
|
+
### `code_run` — Coding backend dispatch
|
|
899
|
+
|
|
900
|
+
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.
|
|
901
|
+
|
|
902
|
+
### `linear_issues` — Native Linear API
|
|
903
|
+
|
|
904
|
+
Agents call `linear_issues` with typed JSON parameters. The tool wraps the Linear GraphQL API directly and handles all name-to-ID resolution automatically.
|
|
905
|
+
|
|
906
|
+
| Action | What it does | Key parameters |
|
|
907
|
+
|---|---|---|
|
|
908
|
+
| `read` | Get full issue details (status, labels, comments, relations) | `issueId` |
|
|
909
|
+
| `create` | Create a new issue or sub-issue | `title`, `description`, `teamId` or `parentIssueId` |
|
|
910
|
+
| `update` | Change status, priority, labels, estimate, or title | `issueId` + fields |
|
|
911
|
+
| `comment` | Post a comment on an issue | `issueId`, `body` |
|
|
912
|
+
| `list_states` | Get available workflow states for a team | `teamId` |
|
|
913
|
+
| `list_labels` | Get available labels for a team | `teamId` |
|
|
914
|
+
|
|
915
|
+
**Sub-issues:** Use `action="create"` with `parentIssueId` to create sub-issues under an existing issue. The new issue inherits `teamId` and `projectId` from its parent automatically. Agents are instructed to break large work into sub-issues for granular tracking — any task with multiple distinct deliverables should be decomposed. Auditors can also create sub-issues for remaining work when an implementation is partial.
|
|
916
|
+
|
|
917
|
+
### `spawn_agent` / `ask_agent` — Multi-agent orchestration
|
|
918
|
+
|
|
919
|
+
Delegate work to other crew agents. `spawn_agent` is fire-and-forget (parallel), `ask_agent` waits for a reply (synchronous). Disabled with `enableOrchestration: false`.
|
|
920
|
+
|
|
921
|
+
### `dispatch_history` — Recent dispatch context
|
|
922
|
+
|
|
923
|
+
Returns recent dispatch activity. Agents use this for situational awareness when working on related issues.
|
|
924
|
+
|
|
925
|
+
### Access model
|
|
926
|
+
|
|
927
|
+
Not all agents get write access. The webhook prompts enforce this:
|
|
928
|
+
|
|
929
|
+
| Context | `linear_issues` access | `code_run` |
|
|
930
|
+
|---|---|---|
|
|
931
|
+
| Triaged issue (In Progress, etc.) | Full (read + create + update + comment) | Yes |
|
|
932
|
+
| Untriaged issue (Backlog, Triage) | Read only | Yes |
|
|
933
|
+
| Auditor | Full (read + create + update + comment) | Yes |
|
|
934
|
+
| Worker (inside `code_run`) | None | N/A |
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Linear API & Hook Architecture
|
|
939
|
+
|
|
940
|
+
This section documents every interaction between the plugin and the Linear GraphQL API, the webhook event routing, the hook lifecycle, and the dispatch pipeline internals.
|
|
941
|
+
|
|
942
|
+
### GraphQL API Layer
|
|
943
|
+
|
|
944
|
+
All Linear API calls go through `LinearAgentApi` (`src/api/linear-api.ts`), which wraps `https://api.linear.app/graphql` with automatic token refresh, retry resilience, and 401 recovery.
|
|
945
|
+
|
|
946
|
+
**Token resolution** (`resolveLinearToken`) checks three sources in priority order:
|
|
947
|
+
|
|
948
|
+
1. `pluginConfig.accessToken` — static config
|
|
949
|
+
2. Auth profile store (`~/.openclaw/auth-profiles.json`) — OAuth tokens with auto-refresh
|
|
950
|
+
3. `LINEAR_ACCESS_TOKEN` / `LINEAR_API_KEY` environment variable
|
|
951
|
+
|
|
952
|
+
OAuth tokens get a `Bearer` prefix; personal API keys do not. Tokens are refreshed 60 seconds before expiry via `refreshLinearToken()`, and the refreshed credentials are persisted back to the auth profile store.
|
|
953
|
+
|
|
954
|
+
**API methods by category:**
|
|
955
|
+
|
|
956
|
+
| Category | Method | GraphQL Operation | Used By |
|
|
957
|
+
|---|---|---|---|
|
|
958
|
+
| **Issues** | `getIssueDetails(issueId)` | `query Issue` | Triage, audit, close, `linear_issues` tool |
|
|
959
|
+
| | `createIssue(input)` | `mutation IssueCreate` | Planner |
|
|
960
|
+
| | `updateIssue(issueId, input)` | `mutation IssueUpdate` | Triage (labels, estimate, priority) |
|
|
961
|
+
| | `updateIssueExtended(issueId, input)` | `mutation IssueUpdate` | `linear_issues` tool, close handler |
|
|
962
|
+
| | `createIssueRelation(input)` | `mutation IssueRelationCreate` | Planner (dependency DAG) |
|
|
963
|
+
| **Comments** | `createComment(issueId, body, opts)` | `mutation CommentCreate` | All phases (fallback delivery) |
|
|
964
|
+
| | `createReaction(commentId, emoji)` | `mutation ReactionCreate` | Acknowledgment reactions |
|
|
965
|
+
| **Sessions** | `createSessionOnIssue(issueId)` | `mutation AgentSessionCreateOnIssue` | Comment handler, close handler |
|
|
966
|
+
| | `emitActivity(sessionId, content)` | `mutation AgentActivityCreate` | Primary response delivery |
|
|
967
|
+
| | `updateSession(sessionId, input)` | `mutation AgentSessionUpdate` | External URLs, plan text |
|
|
968
|
+
| **Teams** | `getTeamStates(teamId)` | `query TeamStates` | `linear_issues` tool, close handler |
|
|
969
|
+
| | `getTeamLabels(teamId)` | `query TeamLabels` | `linear_issues` tool, triage |
|
|
970
|
+
| | `getTeams()` | `query Teams` | Doctor health check |
|
|
971
|
+
| | `createLabel(teamId, name, opts)` | `mutation IssueLabelCreate` | Triage (auto-create labels) |
|
|
972
|
+
| **Projects** | `getProject(projectId)` | `query Project` | Planner |
|
|
973
|
+
| | `getProjectIssues(projectId)` | `query ProjectIssues` | Planner, DAG dispatch |
|
|
974
|
+
| **Webhooks** | `listWebhooks()` | `query Webhooks` | Doctor, webhook setup CLI |
|
|
975
|
+
| | `createWebhook(input)` | `mutation WebhookCreate` | Webhook setup CLI |
|
|
976
|
+
| | `updateWebhook(id, input)` | `mutation WebhookUpdate` | Webhook management |
|
|
977
|
+
| | `deleteWebhook(id)` | `mutation WebhookDelete` | Webhook cleanup |
|
|
978
|
+
| **Notifications** | `getAppNotifications(count)` | `query Notifications` | Doctor (connectivity check) |
|
|
979
|
+
| **Identity** | `getViewerId()` | `query Viewer` | Self-comment filtering |
|
|
980
|
+
|
|
981
|
+
### Webhook Event Routing
|
|
982
|
+
|
|
983
|
+
The plugin registers an HTTP route at `/linear/webhook` that receives POST payloads from two Linear webhook sources:
|
|
984
|
+
|
|
985
|
+
1. **Workspace webhook** — `Comment.create`, `Issue.update`, `Issue.create`
|
|
986
|
+
2. **OAuth app webhook** — `AgentSessionEvent.created`, `AgentSessionEvent.prompted`
|
|
987
|
+
|
|
988
|
+
Both must point to the same URL. `AgentSessionEvent` payloads carry workspace/team guidance which is extracted, cached per-team, and appended to all agent prompts. Comment webhook paths use the cached guidance since Linear does not include guidance in `Comment.create` payloads. See [Linear Guidance](#linear-guidance).
|
|
989
|
+
|
|
990
|
+
The handler dispatches by `type + action`:
|
|
991
|
+
|
|
992
|
+
```
|
|
993
|
+
Incoming POST /linear/webhook
|
|
994
|
+
│
|
|
995
|
+
├─ type=AgentSessionEvent, action=created
|
|
996
|
+
│ └─ New agent session on issue → dedup → create LinearAgentApi → run agent
|
|
997
|
+
│
|
|
998
|
+
├─ type=AgentSessionEvent, action=prompted
|
|
999
|
+
│ └─ Follow-up message in existing session → dedup → resume agent with context
|
|
1000
|
+
│
|
|
1001
|
+
├─ type=Comment, action=create
|
|
1002
|
+
│ └─ Comment on issue → filter self-comments (viewerId) → dedup →
|
|
1003
|
+
│ intent classify → route to handler (see Intent Classification below)
|
|
1004
|
+
│
|
|
1005
|
+
├─ type=Issue, action=update
|
|
1006
|
+
│ └─ Issue field changed → check assignment → if assigned to app user →
|
|
1007
|
+
│ dispatch (triage or full implementation)
|
|
1008
|
+
│
|
|
1009
|
+
├─ type=Issue, action=create
|
|
1010
|
+
│ └─ New issue created → triage (estimate, labels, priority)
|
|
1011
|
+
│
|
|
1012
|
+
└─ type=AppUserNotification
|
|
1013
|
+
└─ Immediately discarded (duplicates workspace webhook events)
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Intent Classification
|
|
1017
|
+
|
|
1018
|
+
When a `Comment.create` event arrives, the plugin classifies the user's intent using a two-tier system:
|
|
1019
|
+
|
|
1020
|
+
1. **LLM classifier** (~300 tokens, ~2-5s) — a small/fast model parses the comment and returns structured JSON with intent + reasoning
|
|
1021
|
+
2. **Regex fallback** — if the LLM call fails or times out, static patterns catch common cases
|
|
1022
|
+
|
|
1023
|
+
| Intent | Trigger | Handler |
|
|
1024
|
+
|---|---|---|
|
|
1025
|
+
| `plan_start` | "let's plan the features" | Start planner interview session |
|
|
1026
|
+
| `plan_finalize` | "looks good, ship it" | Run plan audit + cross-model review |
|
|
1027
|
+
| `plan_abandon` | "cancel planning" | End planning session |
|
|
1028
|
+
| `plan_continue` | Any message during active planning | Continue planner conversation |
|
|
1029
|
+
| `ask_agent` | "@kaylee" or "hey kaylee" | Route to specific agent by name |
|
|
1030
|
+
| `request_work` | "fix the search bug" | Dispatch to default agent |
|
|
1031
|
+
| `question` | "what's the status?" | Agent answers without code changes |
|
|
1032
|
+
| `close_issue` | "close this" / "mark as done" | Generate closure report + transition state |
|
|
1033
|
+
| `general` | Noise, automated messages | Silently dropped |
|
|
1034
|
+
|
|
1035
|
+
### Hook Lifecycle
|
|
1036
|
+
|
|
1037
|
+
The plugin registers three lifecycle hooks via `api.on()` in `index.ts`:
|
|
1038
|
+
|
|
1039
|
+
**`agent_end`** — Dispatch pipeline state machine. When a sub-agent (worker or auditor) finishes:
|
|
1040
|
+
- Looks up the session key in dispatch state to find the active dispatch
|
|
1041
|
+
- Validates the attempt number matches (rejects stale events from old retries)
|
|
1042
|
+
- If the worker finished → triggers the audit phase (`triggerAudit`)
|
|
1043
|
+
- If the auditor finished → processes the verdict (`processVerdict` → pass/fail/stuck)
|
|
1044
|
+
|
|
1045
|
+
**`before_agent_start`** — Context injection. For `linear-worker-*` and `linear-audit-*` sessions:
|
|
1046
|
+
- Reads dispatch state and finds up to 3 active dispatches
|
|
1047
|
+
- Prepends a `<dispatch-history>` block so the agent has situational awareness of concurrent work
|
|
1048
|
+
|
|
1049
|
+
**`message_sending`** — Narration guard. Catches short (~250 char) "Let me explore..." responses where the agent narrates intent without actually calling tools:
|
|
1050
|
+
- Appends a warning: "Agent acknowledged but may not have completed the task"
|
|
1051
|
+
- Prevents users from thinking the agent did something when it only said it would
|
|
1052
|
+
|
|
1053
|
+
### Response Delivery
|
|
1054
|
+
|
|
1055
|
+
Agent responses follow an **emitActivity-first** pattern:
|
|
1056
|
+
|
|
1057
|
+
1. Try `emitActivity(sessionId, { type: "response", body })` — appears as agent activity in Linear's UI, no duplicate comment
|
|
1058
|
+
2. If `emitActivity` fails (no session, API error) → fall back to `createComment(issueId, body)`
|
|
1059
|
+
3. Comments posted outside sessions use `createCommentWithDedup()` — pre-registers the comment ID to prevent the echo webhook from triggering reprocessing
|
|
1060
|
+
|
|
1061
|
+
### Close Issue Flow
|
|
1062
|
+
|
|
1063
|
+
When intent classification returns `close_issue`:
|
|
1064
|
+
|
|
1065
|
+
```
|
|
1066
|
+
close_issue intent
|
|
1067
|
+
│
|
|
1068
|
+
├─ Fetch full issue details (getIssueDetails)
|
|
1069
|
+
├─ Find team's "completed" state (getTeamStates → type=completed)
|
|
1070
|
+
├─ Create agent session on issue (createSessionOnIssue)
|
|
1071
|
+
├─ Emit "preparing closure report" thought (emitActivity)
|
|
1072
|
+
├─ Run agent in read-only mode to generate closure report (runAgent)
|
|
1073
|
+
├─ Transition issue state to completed (updateIssue → stateId)
|
|
1074
|
+
└─ Post closure report (emitActivity → createComment fallback)
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
This is a **static action** — the intent triggers direct API calls orchestrated by the plugin, not by giving the agent write tools. The agent only generates the closure report text; all state transitions are handled by the plugin.
|
|
1078
|
+
|
|
1079
|
+
### Dispatch Pipeline Internals
|
|
1080
|
+
|
|
1081
|
+
The full dispatch flow for implementing an issue:
|
|
1082
|
+
|
|
1083
|
+
```
|
|
1084
|
+
Issue assigned to app user
|
|
1085
|
+
│
|
|
1086
|
+
├─ 1. Assess complexity tier (runAgent → junior/medior/senior)
|
|
1087
|
+
├─ 2. Create isolated git worktree (createWorktree)
|
|
1088
|
+
├─ 3. Register dispatch in state file (registerDispatch)
|
|
1089
|
+
├─ 4. Write .claw/manifest.json with issue metadata
|
|
1090
|
+
├─ 5. Notify: "dispatched as {tier}"
|
|
1091
|
+
│
|
|
1092
|
+
├─ 6. Worker phase (spawnWorker)
|
|
1093
|
+
│ ├─ Build prompt from prompts.yaml (worker.system + worker.task)
|
|
1094
|
+
│ ├─ If retry: append rework.addendum with prior audit gaps
|
|
1095
|
+
│ ├─ Tool access: code_run YES, linear_issues NO
|
|
1096
|
+
│ └─ Output captured as text → saved to .claw/worker-{attempt}.md
|
|
1097
|
+
│
|
|
1098
|
+
├─ 7. Audit phase (triggerAudit)
|
|
1099
|
+
│ ├─ Build prompt from prompts.yaml (audit.system + audit.task)
|
|
1100
|
+
│ ├─ Tool access: code_run YES, linear_issues READ+WRITE
|
|
1101
|
+
│ ├─ Auditor verifies acceptance criteria, runs tests, reviews diff
|
|
1102
|
+
│ └─ Must return JSON verdict: {pass, criteria, gaps, testResults}
|
|
1103
|
+
│
|
|
1104
|
+
└─ 8. Verdict (processVerdict)
|
|
1105
|
+
├─ PASS → updateIssue(stateId=Done), post summary, notify ✅
|
|
1106
|
+
├─ FAIL + retries left → back to step 6 with audit gaps as context
|
|
1107
|
+
└─ FAIL + no retries → escalate, notify 🚨, status="stuck"
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
**State persistence:** Dispatch state is written to `~/.openclaw/linear-dispatch-state.json` with active dispatches, completed history, session mappings, and processed event IDs.
|
|
1111
|
+
|
|
1112
|
+
**Watchdog:** A configurable inactivity timer (`inactivitySec`, default 120s) monitors agent output. If no tool calls or text output for the configured period, the agent process is killed and retried once. If the retry also times out, the dispatch is escalated.
|
|
1113
|
+
|
|
1114
|
+
### `linear_issues` Tool → API Mapping
|
|
1115
|
+
|
|
1116
|
+
The `linear_issues` registered tool translates agent requests into `LinearAgentApi` method calls:
|
|
1117
|
+
|
|
1118
|
+
| Tool Action | API Methods Called |
|
|
1119
|
+
|---|---|
|
|
1120
|
+
| `read` | `getIssueDetails(issueId)` |
|
|
1121
|
+
| `create` | `getIssueDetails(parentIssueId)` (if parent) → `getTeamLabels` (if labels) → `createIssue(input)` |
|
|
1122
|
+
| `update` | `getIssueDetails` → `getTeamStates` (if status) → `getTeamLabels` (if labels) → `updateIssueExtended` |
|
|
1123
|
+
| `comment` | `createComment(issueId, body)` |
|
|
1124
|
+
| `list_states` | `getTeamStates(teamId)` |
|
|
1125
|
+
| `list_labels` | `getTeamLabels(teamId)` |
|
|
1126
|
+
|
|
1127
|
+
The `update` action's key feature is **name-to-ID resolution**: agents say `status: "In Progress"` and the tool automatically resolves it to the correct `stateId` via `getTeamStates`. Same for labels — `labels: ["bug", "urgent"]` resolves to `labelIds` via `getTeamLabels`. Case-insensitive matching with descriptive errors when names don't match.
|
|
1128
|
+
|
|
1129
|
+
The `create` action supports **sub-issue creation** via `parentIssueId`. When provided, the new issue inherits `teamId` and `projectId` from the parent, and the `GraphQL-Features: sub_issues` header is sent automatically. Agents are instructed to decompose large tasks into sub-issues for granular planning and parallel dispatch.
|
|
1130
|
+
|
|
1131
|
+
---
|
|
1132
|
+
|
|
859
1133
|
## Testing & Verification
|
|
860
1134
|
|
|
861
1135
|
### Health check
|
|
@@ -904,9 +1178,45 @@ Example output:
|
|
|
904
1178
|
|
|
905
1179
|
Every warning and error includes a `→` line telling you what to do. Run `doctor --fix` to auto-repair what it can.
|
|
906
1180
|
|
|
1181
|
+
### Code-run health check
|
|
1182
|
+
|
|
1183
|
+
For deeper diagnostics on coding tool backends (Claude Code, Codex, Gemini CLI), run the dedicated code-run doctor. It checks binary installation, API key configuration, and actually invokes each backend to verify it can authenticate and respond:
|
|
1184
|
+
|
|
1185
|
+
```bash
|
|
1186
|
+
openclaw openclaw-linear code-run doctor
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Example output:
|
|
1190
|
+
|
|
1191
|
+
```
|
|
1192
|
+
Code Run: Claude Code (Anthropic)
|
|
1193
|
+
✓ Binary: 2.1.50 (/home/claw/.npm-global/bin/claude)
|
|
1194
|
+
✓ API key: configured (ANTHROPIC_API_KEY)
|
|
1195
|
+
✓ Live test: responded in 3.2s
|
|
1196
|
+
|
|
1197
|
+
Code Run: Codex (OpenAI)
|
|
1198
|
+
✓ Binary: 0.101.0 (/home/claw/.npm-global/bin/codex)
|
|
1199
|
+
✓ API key: configured (OPENAI_API_KEY)
|
|
1200
|
+
✓ Live test: responded in 2.8s
|
|
1201
|
+
|
|
1202
|
+
Code Run: Gemini CLI (Google)
|
|
1203
|
+
✓ Binary: 0.28.2 (/home/claw/.npm-global/bin/gemini)
|
|
1204
|
+
✓ API key: configured (GEMINI_API_KEY)
|
|
1205
|
+
✓ Live test: responded in 4.1s
|
|
1206
|
+
|
|
1207
|
+
Code Run: Routing
|
|
1208
|
+
✓ Default backend: codex
|
|
1209
|
+
✓ Mal → codex (default)
|
|
1210
|
+
✓ Kaylee → codex (default)
|
|
1211
|
+
✓ Inara → claude (override)
|
|
1212
|
+
✓ Callable backends: 3/3
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
This is separate from the main `doctor` because each live test spawns a real CLI subprocess (~5-10s per backend). Use `--json` for machine-readable output.
|
|
1216
|
+
|
|
907
1217
|
### Unit tests
|
|
908
1218
|
|
|
909
|
-
|
|
1219
|
+
551 tests covering the full pipeline — triage, dispatch, audit, planning, intent classification, native issue tools, cross-model review, notifications, and infrastructure:
|
|
910
1220
|
|
|
911
1221
|
```bash
|
|
912
1222
|
cd ~/claw-extensions/linear
|
|
@@ -1069,6 +1379,10 @@ openclaw openclaw-linear webhooks delete <id> # Delete a webhook by ID
|
|
|
1069
1379
|
openclaw openclaw-linear doctor # Run health checks
|
|
1070
1380
|
openclaw openclaw-linear doctor --fix # Auto-fix issues
|
|
1071
1381
|
openclaw openclaw-linear doctor --json # JSON output
|
|
1382
|
+
|
|
1383
|
+
# Code-run backends
|
|
1384
|
+
openclaw openclaw-linear code-run doctor # Deep check all backends (binary, API key, live test)
|
|
1385
|
+
openclaw openclaw-linear code-run doctor --json # JSON output
|
|
1072
1386
|
```
|
|
1073
1387
|
|
|
1074
1388
|
---
|
|
@@ -1089,7 +1403,8 @@ journalctl --user -u openclaw-gateway -f # Watch live logs
|
|
|
1089
1403
|
|---|---|
|
|
1090
1404
|
| Agent goes silent | Watchdog auto-kills after `inactivitySec` and retries. Check logs for `Watchdog KILL`. |
|
|
1091
1405
|
| Dispatch stuck after watchdog | Both retries failed. Check `.claw/log.jsonl`. Re-assign issue to restart. |
|
|
1092
|
-
| `code_run` uses wrong backend | Check `coding-tools.json` — explicit backend > per-agent > global default. |
|
|
1406
|
+
| `code_run` uses wrong backend | Check `coding-tools.json` — explicit backend > per-agent > global default. Run `code-run doctor` to see routing. |
|
|
1407
|
+
| `code_run` fails at runtime | Run `openclaw openclaw-linear code-run doctor` — checks binary, API key, and live callability for each backend. |
|
|
1093
1408
|
| Webhook events not arriving | Run `openclaw openclaw-linear webhooks setup` to auto-provision. Both webhooks must point to `/linear/webhook`. Check tunnel is running. |
|
|
1094
1409
|
| OAuth token expired | Auto-refreshes. If stuck, re-run `openclaw openclaw-linear auth` and restart. |
|
|
1095
1410
|
| Audit always fails | Run `openclaw openclaw-linear prompts validate` to check prompt syntax. |
|
|
@@ -1109,6 +1424,7 @@ For detailed diagnostics, see [docs/troubleshooting.md](docs/troubleshooting.md)
|
|
|
1109
1424
|
|
|
1110
1425
|
- [Architecture](docs/architecture.md) — Internal design, state machines, diagrams
|
|
1111
1426
|
- [Troubleshooting](docs/troubleshooting.md) — Diagnostic commands, curl examples, log analysis
|
|
1427
|
+
- [Agents in Linear](https://linear.app/docs/agents-in-linear) — Linear's agent guidance system (workspace & team-level instructions)
|
|
1112
1428
|
|
|
1113
1429
|
---
|
|
1114
1430
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -59,7 +59,9 @@
|
|
|
59
59
|
"inactivitySec": { "type": "number", "description": "Kill sessions with no I/O for this many seconds (default: 120)", "default": 120 },
|
|
60
60
|
"maxTotalSec": { "type": "number", "description": "Max total runtime for agent sessions in seconds (default: 7200)", "default": 7200 },
|
|
61
61
|
"toolTimeoutSec": { "type": "number", "description": "Max runtime for a single code_run CLI invocation in seconds (default: 600)", "default": 600 },
|
|
62
|
-
"claudeApiKey": { "type": "string", "description": "Anthropic API key for Claude CLI backend (passed as ANTHROPIC_API_KEY env var)", "sensitive": true }
|
|
62
|
+
"claudeApiKey": { "type": "string", "description": "Anthropic API key for Claude CLI backend (passed as ANTHROPIC_API_KEY env var)", "sensitive": true },
|
|
63
|
+
"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" } }
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
}
|
package/package.json
CHANGED
package/prompts.yaml
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# prompts.yaml — Externalized phase prompts for the Linear dispatch pipeline.
|
|
2
2
|
#
|
|
3
3
|
# Template variables: {{identifier}}, {{title}}, {{description}},
|
|
4
|
-
# {{worktreePath}}, {{gaps}}, {{tier}}, {{attempt}}
|
|
4
|
+
# {{worktreePath}}, {{gaps}}, {{tier}}, {{attempt}}, {{guidance}}
|
|
5
5
|
#
|
|
6
6
|
# Edit these to customize worker/audit behavior without rebuilding the plugin.
|
|
7
7
|
# Override path via `promptsPath` in plugin config.
|
|
8
8
|
#
|
|
9
9
|
# Access model:
|
|
10
|
-
#
|
|
11
|
-
# Worker
|
|
12
|
-
# Auditor
|
|
10
|
+
# Orchestrator — linear_issues READ + CREATE (action=read, create, list_states, list_labels)
|
|
11
|
+
# Worker — NO linear_issues access. Return text output only.
|
|
12
|
+
# Auditor — linear_issues READ + WRITE (can update status, close, comment, create sub-issues)
|
|
13
13
|
|
|
14
14
|
worker:
|
|
15
15
|
system: |
|
|
16
16
|
You are a coding worker implementing a Linear issue. Your ONLY job is to write code and return a text summary.
|
|
17
|
-
You do NOT have access to
|
|
17
|
+
You do NOT have access to linear_issues or any Linear issue management tools.
|
|
18
18
|
Do NOT attempt to update, close, comment on, or modify the Linear issue in any way.
|
|
19
19
|
Do NOT mark the issue as Done — the audit system handles all issue lifecycle.
|
|
20
20
|
Just write code and return your implementation summary as text.
|
|
@@ -36,7 +36,8 @@ worker:
|
|
|
36
36
|
7. Commit your work with a clear commit message
|
|
37
37
|
8. Return a text summary: what you changed, what tests passed, any assumptions you made, and any open questions
|
|
38
38
|
|
|
39
|
-
Your text output will be captured automatically. Do NOT use
|
|
39
|
+
Your text output will be captured automatically. Do NOT use linear_issues or attempt to post comments.
|
|
40
|
+
{{guidance}}
|
|
40
41
|
|
|
41
42
|
audit:
|
|
42
43
|
system: |
|
|
@@ -46,10 +47,12 @@ audit:
|
|
|
46
47
|
Worker output is secondary evidence of what was attempted.
|
|
47
48
|
You must be thorough and objective. Do not rubber-stamp.
|
|
48
49
|
|
|
49
|
-
You have WRITE access to
|
|
50
|
+
You have WRITE access to the `linear_issues` tool. After auditing, you are responsible for:
|
|
50
51
|
- Posting an audit summary comment on the issue
|
|
51
52
|
- Updating the issue status if the audit passes
|
|
52
|
-
|
|
53
|
+
- If work is partially done but remaining items are clearly separable, create sub-issues
|
|
54
|
+
for unfinished work using action="create" with parentIssueId="{{identifier}}"
|
|
55
|
+
Use the `linear_issues` tool for these operations.
|
|
53
56
|
task: |
|
|
54
57
|
Audit issue {{identifier}}: {{title}}
|
|
55
58
|
|
|
@@ -60,21 +63,27 @@ audit:
|
|
|
60
63
|
|
|
61
64
|
Checklist:
|
|
62
65
|
1. Identify ALL acceptance criteria from the issue body
|
|
63
|
-
2. Read worker comments: `
|
|
66
|
+
2. Read worker comments: `linear_issues` with action="read", issueId="{{identifier}}"
|
|
64
67
|
3. Verify each acceptance criterion is addressed in the code
|
|
65
68
|
4. Run tests in the worktree — verify they pass
|
|
66
69
|
5. Check test coverage if expectations are stated in the issue
|
|
67
70
|
6. Review the code diff for quality and correctness
|
|
68
71
|
|
|
69
72
|
After auditing:
|
|
70
|
-
- Post your audit findings as a comment: `
|
|
71
|
-
- If PASS: update status: `
|
|
73
|
+
- Post your audit findings as a comment: `linear_issues` with action="comment", issueId="{{identifier}}", body="..."
|
|
74
|
+
- If PASS: update status: `linear_issues` with action="update", issueId="{{identifier}}", status="Done"
|
|
75
|
+
- If PARTIAL PASS (some criteria met, others clearly separable): pass the completed work,
|
|
76
|
+
then create sub-issues for remaining items using `linear_issues` with action="create",
|
|
77
|
+
parentIssueId="{{identifier}}", title="...", description="..." (include acceptance criteria).
|
|
78
|
+
This is better than failing the entire audit when significant progress was made.
|
|
72
79
|
|
|
73
80
|
When posting your audit comment, include a brief assessment: what was done well,
|
|
74
81
|
what the gaps are (if any), and what the user should look at next.
|
|
82
|
+
If you created sub-issues, list them in your comment.
|
|
75
83
|
|
|
76
84
|
You MUST return a JSON verdict as the last line of your response:
|
|
77
85
|
{"pass": true/false, "criteria": ["list of criteria found"], "gaps": ["list of unmet criteria"], "testResults": "summary of test output"}
|
|
86
|
+
{{guidance}}
|
|
78
87
|
|
|
79
88
|
rework:
|
|
80
89
|
addendum: |
|
|
@@ -83,7 +92,7 @@ rework:
|
|
|
83
92
|
|
|
84
93
|
Address these specific issues in your rework. Focus ONLY on the gaps listed above.
|
|
85
94
|
Do NOT undo or rewrite parts that already work — preserve correct code from prior attempts.
|
|
86
|
-
Remember: you do NOT have
|
|
95
|
+
Remember: you do NOT have linear_issues access. Just fix the code and return a text summary.
|
|
87
96
|
|
|
88
97
|
planner:
|
|
89
98
|
system: |
|
|
@@ -95,6 +104,9 @@ planner:
|
|
|
95
104
|
- Epics are high-level feature areas. Mark them with isEpic=true.
|
|
96
105
|
- Issues under epics are concrete deliverables with acceptance criteria.
|
|
97
106
|
- Sub-issues are atomic work units that together complete a parent issue.
|
|
107
|
+
Use sub-issues aggressively — any issue estimated at 3+ story points should be
|
|
108
|
+
decomposed into smaller sub-issues (1-2 points each). Each sub-issue must be
|
|
109
|
+
independently implementable and testable.
|
|
98
110
|
- Use "blocks" relationships to express ordering: if A must finish before B starts, A blocks B.
|
|
99
111
|
- Every non-epic issue needs a story point estimate and priority.
|
|
100
112
|
- Every issue description must include:
|
|
@@ -11,7 +11,8 @@ export function makeIssueDetails(overrides?: Record<string, unknown>) {
|
|
|
11
11
|
title: "Fix webhook routing",
|
|
12
12
|
description: "The webhook handler needs fixing.",
|
|
13
13
|
estimate: 3,
|
|
14
|
-
state: { name: "In Progress" },
|
|
14
|
+
state: { name: "In Progress", type: "started" },
|
|
15
|
+
creator: { name: "Test User", email: "test@example.com" },
|
|
15
16
|
assignee: { name: "Agent" },
|
|
16
17
|
labels: { nodes: [] as Array<{ id: string; name: string }> },
|
|
17
18
|
team: { id: "team-1", name: "Engineering", issueEstimationType: "notUsed" },
|
|
@@ -111,8 +111,14 @@ export function makeAgentSessionEventCreated(overrides?: Record<string, unknown>
|
|
|
111
111
|
title: "Fix webhook routing",
|
|
112
112
|
},
|
|
113
113
|
},
|
|
114
|
-
previousComments: [
|
|
115
|
-
|
|
114
|
+
previousComments: [
|
|
115
|
+
{
|
|
116
|
+
body: "Please investigate this issue",
|
|
117
|
+
userId: "user-1",
|
|
118
|
+
createdAt: new Date().toISOString(),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
guidance: "Always use the main branch. Run make test before closing.",
|
|
116
122
|
...overrides,
|
|
117
123
|
};
|
|
118
124
|
}
|
|
@@ -133,6 +139,7 @@ export function makeAgentSessionEventPrompted(overrides?: Record<string, unknown
|
|
|
133
139
|
},
|
|
134
140
|
},
|
|
135
141
|
agentActivity: { content: { body: "Follow-up question here" } },
|
|
142
|
+
promptContext: "## Issue\nENG-123: Fix webhook routing\n\n## Guidance\nAlways run lint before committing.\n\n## Comments\nSome prior thread.",
|
|
136
143
|
webhookId: "webhook-prompted-1",
|
|
137
144
|
...overrides,
|
|
138
145
|
};
|