@calltelemetry/openclaw-linear 0.9.3 → 0.9.4
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/package.json +1 -1
- package/src/__test__/smoke-linear-api.test.ts +2 -1
- package/src/__test__/webhook-scenarios.test.ts +3 -0
- package/src/agent/agent.ts +9 -7
- package/src/agent/watchdog.ts +1 -1
- package/src/api/linear-api.ts +2 -1
- package/src/api/oauth-callback.ts +2 -1
- package/src/infra/cli.ts +2 -2
- package/src/infra/codex-worktree.ts +2 -2
- package/src/infra/config-paths.test.ts +3 -0
- package/src/infra/doctor.test.ts +621 -1
- package/src/infra/multi-repo.test.ts +11 -9
- package/src/infra/multi-repo.ts +3 -2
- package/src/infra/shared-profiles.ts +2 -1
- package/src/infra/template.test.ts +2 -2
- package/src/pipeline/active-session.test.ts +96 -1
- package/src/pipeline/active-session.ts +60 -0
- package/src/pipeline/artifacts.ts +1 -1
- package/src/pipeline/pipeline.test.ts +3 -0
- package/src/pipeline/webhook-dedup.test.ts +3 -0
- package/src/pipeline/webhook.test.ts +2088 -2
- package/src/pipeline/webhook.ts +24 -6
- package/src/tools/claude-tool.ts +1 -1
- package/src/tools/cli-shared.test.ts +3 -0
- package/src/tools/cli-shared.ts +3 -1
- package/src/tools/code-tool.test.ts +3 -0
- package/src/tools/code-tool.ts +1 -1
- package/src/tools/codex-tool.ts +1 -1
- package/src/tools/gemini-tool.ts +1 -1
package/src/pipeline/webhook.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
5
5
|
import { LinearAgentApi, resolveLinearToken } from "../api/linear-api.js";
|
|
6
6
|
import { spawnWorker, type HookContext } from "./pipeline.js";
|
|
7
|
-
import { setActiveSession, clearActiveSession } from "./active-session.js";
|
|
7
|
+
import { setActiveSession, clearActiveSession, getIssueAffinity, _configureAffinityTtl, _resetAffinityForTesting } from "./active-session.js";
|
|
8
8
|
import { readDispatchState, getActiveDispatch, registerDispatch, updateDispatchStatus, completeDispatch, removeActiveDispatch } from "./dispatch-state.js";
|
|
9
9
|
import { createNotifierFromConfig, type NotifyFn } from "../infra/notify.js";
|
|
10
10
|
import { assessTier } from "./tier-assess.js";
|
|
@@ -80,6 +80,7 @@ export function _resetForTesting(): void {
|
|
|
80
80
|
_dedupTtlMs = 60_000;
|
|
81
81
|
_sweepIntervalMs = 10_000;
|
|
82
82
|
_resetGuidanceCacheForTesting();
|
|
83
|
+
_resetAffinityForTesting();
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/** @internal — test-only; add an issue ID to the activeRuns set. */
|
|
@@ -346,6 +347,14 @@ export async function handleLinearWebhook(
|
|
|
346
347
|
}
|
|
347
348
|
}
|
|
348
349
|
}
|
|
350
|
+
// Session affinity: if no @mention override, prefer the agent that last handled this issue
|
|
351
|
+
if (agentId === resolveAgentId(api) && issue?.id) {
|
|
352
|
+
const affinityAgent = getIssueAffinity(issue.id);
|
|
353
|
+
if (affinityAgent) {
|
|
354
|
+
api.logger.info(`AgentSession routed to ${affinityAgent} via session affinity for ${issue.identifier ?? issue.id}`);
|
|
355
|
+
agentId = affinityAgent;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
349
358
|
|
|
350
359
|
api.logger.info(`AgentSession created: ${session.id} for issue ${issue?.identifier ?? issue?.id} agent=${agentId} (comments: ${previousComments.length}, guidance: ${guidanceCtx.guidance ? "yes" : "no"})`);
|
|
351
360
|
|
|
@@ -557,6 +566,14 @@ export async function handleLinearWebhook(
|
|
|
557
566
|
}
|
|
558
567
|
}
|
|
559
568
|
}
|
|
569
|
+
// Session affinity: if no @mention override, prefer the agent that last handled this issue
|
|
570
|
+
if (agentId === resolveAgentId(api) && issue?.id) {
|
|
571
|
+
const affinityAgent = getIssueAffinity(issue.id);
|
|
572
|
+
if (affinityAgent) {
|
|
573
|
+
api.logger.info(`AgentSession prompted: routed to ${affinityAgent} via session affinity for ${issue.identifier ?? issue.id}`);
|
|
574
|
+
agentId = affinityAgent;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
560
577
|
|
|
561
578
|
api.logger.info(`AgentSession prompted (follow-up): ${session.id} issue=${issue?.identifier ?? issue?.id} agent=${agentId} message="${userMessage.slice(0, 80)}..."`);
|
|
562
579
|
|
|
@@ -885,8 +902,9 @@ export async function handleLinearWebhook(
|
|
|
885
902
|
case "plan_continue": {
|
|
886
903
|
if (!isPlanning || !planSession) {
|
|
887
904
|
// Not in planning mode — treat as general
|
|
888
|
-
|
|
889
|
-
|
|
905
|
+
const planContinueAgent = getIssueAffinity(issue.id) ?? resolveAgentId(api);
|
|
906
|
+
api.logger.info(`Comment intent plan_continue but not in planning mode — dispatching to ${planContinueAgent}`);
|
|
907
|
+
void dispatchCommentToAgent(api, linearApi, profiles, planContinueAgent, issue, comment, commentBody, commentor, pluginConfig)
|
|
890
908
|
.catch((err) => api.logger.error(`Comment dispatch error: ${err}`));
|
|
891
909
|
break;
|
|
892
910
|
}
|
|
@@ -908,15 +926,15 @@ export async function handleLinearWebhook(
|
|
|
908
926
|
|
|
909
927
|
case "request_work":
|
|
910
928
|
case "question": {
|
|
911
|
-
const defaultAgent = resolveAgentId(api);
|
|
912
|
-
api.logger.info(`Comment intent ${intentResult.intent}: routing to
|
|
929
|
+
const defaultAgent = getIssueAffinity(issue.id) ?? resolveAgentId(api);
|
|
930
|
+
api.logger.info(`Comment intent ${intentResult.intent}: routing to ${defaultAgent}`);
|
|
913
931
|
void dispatchCommentToAgent(api, linearApi, profiles, defaultAgent, issue, comment, commentBody, commentor, pluginConfig)
|
|
914
932
|
.catch((err) => api.logger.error(`Comment dispatch error: ${err}`));
|
|
915
933
|
break;
|
|
916
934
|
}
|
|
917
935
|
|
|
918
936
|
case "close_issue": {
|
|
919
|
-
const closeAgent = resolveAgentId(api);
|
|
937
|
+
const closeAgent = getIssueAffinity(issue.id) ?? resolveAgentId(api);
|
|
920
938
|
api.logger.info(`Comment intent close_issue: closing ${issue.identifier ?? issue.id} via ${closeAgent}`);
|
|
921
939
|
void handleCloseIssue(api, linearApi, profiles, closeAgent, issue, comment, commentBody, commentor, pluginConfig)
|
|
922
940
|
.catch((err) => api.logger.error(`Close issue error: ${err}`));
|
package/src/tools/claude-tool.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./cli-shared.js";
|
|
14
14
|
import { InactivityWatchdog, resolveWatchdogConfig } from "../agent/watchdog.js";
|
|
15
15
|
|
|
16
|
-
const CLAUDE_BIN = "
|
|
16
|
+
const CLAUDE_BIN = "claude";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Map a Claude Code stream-json JSONL event to a Linear activity.
|
|
@@ -4,6 +4,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
4
4
|
vi.mock("../pipeline/active-session.js", () => ({
|
|
5
5
|
getCurrentSession: vi.fn(() => null),
|
|
6
6
|
getActiveSessionByIdentifier: vi.fn(() => null),
|
|
7
|
+
getIssueAffinity: vi.fn().mockReturnValue(null),
|
|
8
|
+
_configureAffinityTtl: vi.fn(),
|
|
9
|
+
_resetAffinityForTesting: vi.fn(),
|
|
7
10
|
}));
|
|
8
11
|
|
|
9
12
|
// Mock the linear-api module — LinearAgentApi must be a class (used with `new`)
|
package/src/tools/cli-shared.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
1
3
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
4
|
import type { LinearAgentApi } from "../api/linear-api.js";
|
|
3
5
|
import { resolveLinearToken, LinearAgentApi as LinearAgentApiClass } from "../api/linear-api.js";
|
|
@@ -5,7 +7,7 @@ import { getCurrentSession, getActiveSessionByIdentifier } from "../pipeline/act
|
|
|
5
7
|
|
|
6
8
|
export const DEFAULT_TIMEOUT_MS = 10 * 60_000; // 10 minutes (legacy — prefer watchdog config)
|
|
7
9
|
export { DEFAULT_INACTIVITY_SEC, DEFAULT_MAX_TOTAL_SEC, DEFAULT_TOOL_TIMEOUT_SEC } from "../agent/watchdog.js";
|
|
8
|
-
export const DEFAULT_BASE_REPO = "
|
|
10
|
+
export const DEFAULT_BASE_REPO = join(homedir(), "ai-workspace");
|
|
9
11
|
|
|
10
12
|
export interface CliToolParams {
|
|
11
13
|
prompt: string;
|
|
@@ -17,6 +17,9 @@ vi.mock("./gemini-tool.js", () => ({
|
|
|
17
17
|
}));
|
|
18
18
|
vi.mock("../pipeline/active-session.js", () => ({
|
|
19
19
|
getCurrentSession: vi.fn(() => null),
|
|
20
|
+
getIssueAffinity: vi.fn().mockReturnValue(null),
|
|
21
|
+
_configureAffinityTtl: vi.fn(),
|
|
22
|
+
_resetAffinityForTesting: vi.fn(),
|
|
20
23
|
}));
|
|
21
24
|
vi.mock("openclaw/plugin-sdk", () => ({
|
|
22
25
|
jsonResult: vi.fn((v: unknown) => v),
|
package/src/tools/code-tool.ts
CHANGED
|
@@ -164,7 +164,7 @@ export function createCodeTool(
|
|
|
164
164
|
},
|
|
165
165
|
workingDir: {
|
|
166
166
|
type: "string",
|
|
167
|
-
description: "Override working directory (default:
|
|
167
|
+
description: "Override working directory (default: ~/ai-workspace).",
|
|
168
168
|
},
|
|
169
169
|
model: {
|
|
170
170
|
type: "string",
|
package/src/tools/codex-tool.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./cli-shared.js";
|
|
14
14
|
import { InactivityWatchdog, resolveWatchdogConfig } from "../agent/watchdog.js";
|
|
15
15
|
|
|
16
|
-
const CODEX_BIN = "
|
|
16
|
+
const CODEX_BIN = "codex";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Parse a JSONL line from `codex exec --json` and map it to a Linear activity.
|
package/src/tools/gemini-tool.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./cli-shared.js";
|
|
14
14
|
import { InactivityWatchdog, resolveWatchdogConfig } from "../agent/watchdog.js";
|
|
15
15
|
|
|
16
|
-
const GEMINI_BIN = "
|
|
16
|
+
const GEMINI_BIN = "gemini";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Map a Gemini CLI stream-json JSONL event to a Linear activity.
|