@feeble/blay-openclaw-plugin 0.1.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.
Files changed (79) hide show
  1. package/README.md +24 -0
  2. package/dist/channel/abort.d.ts +11 -0
  3. package/dist/channel/abort.js +20 -0
  4. package/dist/channel/abort.js.map +1 -0
  5. package/dist/channel/inbound.d.ts +53 -0
  6. package/dist/channel/inbound.js +384 -0
  7. package/dist/channel/inbound.js.map +1 -0
  8. package/dist/channel/plugin.d.ts +25 -0
  9. package/dist/channel/plugin.js +291 -0
  10. package/dist/channel/plugin.js.map +1 -0
  11. package/dist/channel/run-tracker.d.ts +54 -0
  12. package/dist/channel/run-tracker.js +137 -0
  13. package/dist/channel/run-tracker.js.map +1 -0
  14. package/dist/channel/runtime.d.ts +8 -0
  15. package/dist/channel/runtime.js +16 -0
  16. package/dist/channel/runtime.js.map +1 -0
  17. package/dist/channel/sse-client.d.ts +54 -0
  18. package/dist/channel/sse-client.js +154 -0
  19. package/dist/channel/sse-client.js.map +1 -0
  20. package/dist/channel/state.d.ts +6 -0
  21. package/dist/channel/state.js +11 -0
  22. package/dist/channel/state.js.map +1 -0
  23. package/dist/client.d.ts +23 -0
  24. package/dist/client.js +98 -0
  25. package/dist/client.js.map +1 -0
  26. package/dist/index.d.ts +16 -0
  27. package/dist/index.js +228 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/telemetry/pusher.d.ts +71 -0
  30. package/dist/telemetry/pusher.js +461 -0
  31. package/dist/telemetry/pusher.js.map +1 -0
  32. package/dist/telemetry/usage-collector.d.ts +39 -0
  33. package/dist/telemetry/usage-collector.js +60 -0
  34. package/dist/telemetry/usage-collector.js.map +1 -0
  35. package/dist/tools/action-items.d.ts +60 -0
  36. package/dist/tools/action-items.js +101 -0
  37. package/dist/tools/action-items.js.map +1 -0
  38. package/dist/tools/briefing.d.ts +15 -0
  39. package/dist/tools/briefing.js +138 -0
  40. package/dist/tools/briefing.js.map +1 -0
  41. package/dist/tools/comments.d.ts +43 -0
  42. package/dist/tools/comments.js +79 -0
  43. package/dist/tools/comments.js.map +1 -0
  44. package/dist/tools/context.d.ts +20 -0
  45. package/dist/tools/context.js +31 -0
  46. package/dist/tools/context.js.map +1 -0
  47. package/dist/tools/instrumentation.d.ts +19 -0
  48. package/dist/tools/instrumentation.js +84 -0
  49. package/dist/tools/instrumentation.js.map +1 -0
  50. package/dist/tools/notifications.d.ts +29 -0
  51. package/dist/tools/notifications.js +57 -0
  52. package/dist/tools/notifications.js.map +1 -0
  53. package/dist/tools/org.d.ts +12 -0
  54. package/dist/tools/org.js +31 -0
  55. package/dist/tools/org.js.map +1 -0
  56. package/dist/tools/projects.d.ts +73 -0
  57. package/dist/tools/projects.js +121 -0
  58. package/dist/tools/projects.js.map +1 -0
  59. package/dist/tools/search.d.ts +20 -0
  60. package/dist/tools/search.js +50 -0
  61. package/dist/tools/search.js.map +1 -0
  62. package/dist/tools/tasks.d.ts +110 -0
  63. package/dist/tools/tasks.js +168 -0
  64. package/dist/tools/tasks.js.map +1 -0
  65. package/dist/tools/users.d.ts +12 -0
  66. package/dist/tools/users.js +31 -0
  67. package/dist/tools/users.js.map +1 -0
  68. package/dist/types.d.ts +20 -0
  69. package/dist/types.js +5 -0
  70. package/dist/types.js.map +1 -0
  71. package/dist/utils/formatting.d.ts +16 -0
  72. package/dist/utils/formatting.js +196 -0
  73. package/dist/utils/formatting.js.map +1 -0
  74. package/dist/webhook/handler.d.ts +10 -0
  75. package/dist/webhook/handler.js +70 -0
  76. package/dist/webhook/handler.js.map +1 -0
  77. package/openclaw.plugin.json +21 -0
  78. package/package.json +36 -0
  79. package/skills/blay/SKILL.md +25 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Minimal SSE Client for Node.js
3
+ *
4
+ * Connects to a Server-Sent Events endpoint using fetch streams.
5
+ * Handles reconnection with exponential backoff.
6
+ * No external dependencies — uses Node.js built-in fetch.
7
+ */
8
+ export class SSEClient {
9
+ abortController = null;
10
+ reconnectDelay;
11
+ maxReconnectDelay;
12
+ currentDelay;
13
+ running = false;
14
+ options;
15
+ lastEventId;
16
+ /**
17
+ * Generation counter — incremented on every stop(). Prevents stale
18
+ * reconnection chains from forking when stop() + start() is called
19
+ * while a reconnection sleep is in progress.
20
+ */
21
+ generation = 0;
22
+ constructor(options) {
23
+ this.options = options;
24
+ this.reconnectDelay = options.reconnectDelay ?? 1000;
25
+ this.maxReconnectDelay = options.maxReconnectDelay ?? 30000;
26
+ this.currentDelay = this.reconnectDelay;
27
+ }
28
+ /**
29
+ * Start the SSE connection. Reconnects automatically on failure.
30
+ */
31
+ start() {
32
+ if (this.running)
33
+ return;
34
+ this.running = true;
35
+ this.connectWithGen(this.generation);
36
+ }
37
+ /**
38
+ * Stop the SSE connection permanently.
39
+ */
40
+ stop() {
41
+ this.running = false;
42
+ this.generation++; // Invalidate any pending reconnection chains
43
+ if (this.abortController) {
44
+ this.abortController.abort();
45
+ this.abortController = null;
46
+ }
47
+ }
48
+ async connectWithGen(gen) {
49
+ if (!this.running || gen !== this.generation)
50
+ return;
51
+ this.abortController = new AbortController();
52
+ try {
53
+ const response = await fetch(this.options.url, {
54
+ method: "GET",
55
+ headers: {
56
+ Accept: "text/event-stream",
57
+ "Cache-Control": "no-cache",
58
+ ...this.options.headers,
59
+ ...(this.lastEventId ? { "Last-Event-ID": this.lastEventId } : {}),
60
+ },
61
+ signal: this.abortController.signal,
62
+ });
63
+ if (!response.ok) {
64
+ throw new Error(`SSE connection failed: HTTP ${response.status}`);
65
+ }
66
+ if (!response.body) {
67
+ throw new Error("SSE response has no body");
68
+ }
69
+ // Reset reconnect delay on successful connection
70
+ this.currentDelay = this.reconnectDelay;
71
+ this.options.onConnected?.();
72
+ // Read the stream
73
+ const reader = response.body.getReader();
74
+ const decoder = new TextDecoder();
75
+ let buffer = "";
76
+ while (this.running && gen === this.generation) {
77
+ const { done, value } = await reader.read();
78
+ if (done)
79
+ break;
80
+ buffer += decoder.decode(value, { stream: true });
81
+ // Parse complete SSE events from buffer
82
+ const events = this.parseEvents(buffer);
83
+ buffer = events.remainder;
84
+ for (const event of events.parsed) {
85
+ if (event.id) {
86
+ this.lastEventId = event.id;
87
+ }
88
+ try {
89
+ this.options.onEvent(event);
90
+ }
91
+ catch (err) {
92
+ this.options.onError?.(err instanceof Error ? err : new Error(String(err)));
93
+ }
94
+ }
95
+ }
96
+ }
97
+ catch (err) {
98
+ // Ignore abort errors (intentional disconnect)
99
+ if (err instanceof Error && err.name === "AbortError") {
100
+ return;
101
+ }
102
+ this.options.onError?.(err instanceof Error ? err : new Error(String(err)));
103
+ }
104
+ // Don't fire callbacks or reconnect if this generation was invalidated
105
+ if (gen !== this.generation)
106
+ return;
107
+ this.options.onDisconnected?.();
108
+ // Reconnect if still running and generation hasn't changed
109
+ if (this.running && gen === this.generation) {
110
+ const delay = this.currentDelay;
111
+ this.currentDelay = Math.min(this.currentDelay * 2, this.maxReconnectDelay);
112
+ await new Promise((resolve) => setTimeout(resolve, delay));
113
+ this.connectWithGen(gen);
114
+ }
115
+ }
116
+ /**
117
+ * Parse SSE events from a text buffer.
118
+ * Returns parsed events and the remaining unparsed text.
119
+ */
120
+ parseEvents(buffer) {
121
+ const parsed = [];
122
+ // SSE events are separated by double newlines
123
+ const parts = buffer.split("\n\n");
124
+ // The last part may be incomplete
125
+ const remainder = parts.pop() ?? "";
126
+ for (const part of parts) {
127
+ const trimmed = part.trim();
128
+ if (!trimmed)
129
+ continue;
130
+ let event = "";
131
+ let data = "";
132
+ let id;
133
+ for (const line of trimmed.split("\n")) {
134
+ if (line.startsWith("event: ")) {
135
+ event = line.slice(7);
136
+ }
137
+ else if (line.startsWith("data: ")) {
138
+ data += (data ? "\n" : "") + line.slice(6);
139
+ }
140
+ else if (line.startsWith("id: ")) {
141
+ id = line.slice(4);
142
+ }
143
+ else if (line.startsWith(":")) {
144
+ // Comment, ignore
145
+ }
146
+ }
147
+ if (data || event) {
148
+ parsed.push({ event: event || "message", data, id });
149
+ }
150
+ }
151
+ return { parsed, remainder };
152
+ }
153
+ }
154
+ //# sourceMappingURL=sse-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-client.js","sourceRoot":"","sources":["../../src/channel/sse-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared state — holds the BlayClient reference for use by channel modules.
3
+ */
4
+ import type { BlayClient } from "../client.js";
5
+ export declare function setBlayClient(client: BlayClient): void;
6
+ export declare function getBlayClient(): BlayClient | null;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared state — holds the BlayClient reference for use by channel modules.
3
+ */
4
+ let _client = null;
5
+ export function setBlayClient(client) {
6
+ _client = client;
7
+ }
8
+ export function getBlayClient() {
9
+ return _client;
10
+ }
11
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/channel/state.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,IAAI,OAAO,GAAsB,IAAI,CAAC;AAEtC,MAAM,UAAU,aAAa,CAAC,MAAkB;IAC9C,OAAO,GAAG,MAAM,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * HTTP client for the Blay REST API.
3
+ *
4
+ * Class-based wrapper around fetch with Bearer token auth.
5
+ * All tools use this to call the API.
6
+ */
7
+ import type { ApiResponse, BlayPluginConfig } from "./types.js";
8
+ export declare const DEFAULT_BASE_URL = "https://api.blay.io";
9
+ export declare class BlayClient {
10
+ private baseUrl;
11
+ private apiKey;
12
+ constructor(config: BlayPluginConfig);
13
+ private headers;
14
+ private fetchWithTimeout;
15
+ get<T = unknown>(path: string, params?: Record<string, string | undefined>): Promise<ApiResponse<T>>;
16
+ post<T = unknown>(path: string, body?: Record<string, unknown>): Promise<ApiResponse<T>>;
17
+ patch<T = unknown>(path: string, body: Record<string, unknown>): Promise<ApiResponse<T>>;
18
+ del<T = unknown>(path: string): Promise<ApiResponse<T>>;
19
+ }
20
+ /**
21
+ * Format an API error response into an actionable message for the AI.
22
+ */
23
+ export declare function formatError(res: ApiResponse): string;
package/dist/client.js ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * HTTP client for the Blay REST API.
3
+ *
4
+ * Class-based wrapper around fetch with Bearer token auth.
5
+ * All tools use this to call the API.
6
+ */
7
+ export const DEFAULT_BASE_URL = "https://api.blay.io";
8
+ export class BlayClient {
9
+ baseUrl;
10
+ apiKey;
11
+ constructor(config) {
12
+ this.baseUrl = (config.apiUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
13
+ this.apiKey = config.apiKey;
14
+ }
15
+ headers() {
16
+ return {
17
+ Authorization: `Bearer ${this.apiKey}`,
18
+ "Content-Type": "application/json",
19
+ };
20
+ }
21
+ async fetchWithTimeout(url, init, timeoutMs = 15_000) {
22
+ const controller = new AbortController();
23
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
24
+ try {
25
+ return await fetch(url, { ...init, signal: controller.signal });
26
+ }
27
+ finally {
28
+ clearTimeout(timer);
29
+ }
30
+ }
31
+ async get(path, params) {
32
+ const url = new URL(`${this.baseUrl}${path}`);
33
+ if (params) {
34
+ for (const [key, value] of Object.entries(params)) {
35
+ if (value !== undefined) {
36
+ url.searchParams.set(key, value);
37
+ }
38
+ }
39
+ }
40
+ const res = await this.fetchWithTimeout(url.toString(), {
41
+ method: "GET",
42
+ headers: this.headers(),
43
+ });
44
+ const data = (await res.json());
45
+ return { ok: res.ok, status: res.status, data };
46
+ }
47
+ async post(path, body) {
48
+ const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
49
+ method: "POST",
50
+ headers: this.headers(),
51
+ body: body ? JSON.stringify(body) : undefined,
52
+ });
53
+ const data = (await res.json());
54
+ return { ok: res.ok, status: res.status, data };
55
+ }
56
+ async patch(path, body) {
57
+ const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
58
+ method: "PATCH",
59
+ headers: this.headers(),
60
+ body: JSON.stringify(body),
61
+ });
62
+ const data = (await res.json());
63
+ return { ok: res.ok, status: res.status, data };
64
+ }
65
+ async del(path) {
66
+ const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
67
+ method: "DELETE",
68
+ headers: this.headers(),
69
+ });
70
+ const data = (await res.json());
71
+ return { ok: res.ok, status: res.status, data };
72
+ }
73
+ }
74
+ /**
75
+ * Format an API error response into an actionable message for the AI.
76
+ */
77
+ export function formatError(res) {
78
+ const detail = res.data && typeof res.data === "object" && "message" in res.data
79
+ ? String(res.data.message)
80
+ : undefined;
81
+ switch (res.status) {
82
+ case 401:
83
+ return "Authentication failed. The API key may be invalid or expired.";
84
+ case 403:
85
+ return "Permission denied. You don't have access to this resource.";
86
+ case 404:
87
+ return "Not found. The ID may be incorrect — use search or list tools to find the right one.";
88
+ case 400:
89
+ case 422:
90
+ return `Invalid input${detail ? `: ${detail}` : ". Check the parameters and try again."}`;
91
+ default:
92
+ if (res.status >= 500) {
93
+ return "Server error. Try again in a moment.";
94
+ }
95
+ return detail ?? `Request failed (HTTP ${res.status}).`;
96
+ }
97
+ }
98
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Blay OpenClaw Plugin — Entry Point
3
+ *
4
+ * Registers Blay as a native OpenClaw channel and provides 21 tools
5
+ * for the agent to interact with the Blay platform.
6
+ *
7
+ * Architecture:
8
+ * - Channel: Blay is registered as a native channel via registerChannel().
9
+ * Inbound events arrive via a persistent SSE connection to the Blay API.
10
+ * Agent responses are automatically delivered as Blay comments.
11
+ * - Tools: blay_* tools give the agent direct API access for reading/writing
12
+ * Blay data (projects, tasks, comments, etc.)
13
+ * - Webhook: HTTP route still available as fallback for webhook-based delivery.
14
+ */
15
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
16
+ export default function register(api: OpenClawPluginApi): void;
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Blay OpenClaw Plugin — Entry Point
3
+ *
4
+ * Registers Blay as a native OpenClaw channel and provides 21 tools
5
+ * for the agent to interact with the Blay platform.
6
+ *
7
+ * Architecture:
8
+ * - Channel: Blay is registered as a native channel via registerChannel().
9
+ * Inbound events arrive via a persistent SSE connection to the Blay API.
10
+ * Agent responses are automatically delivered as Blay comments.
11
+ * - Tools: blay_* tools give the agent direct API access for reading/writing
12
+ * Blay data (projects, tasks, comments, etc.)
13
+ * - Webhook: HTTP route still available as fallback for webhook-based delivery.
14
+ */
15
+ import { BlayClient } from "./client.js";
16
+ // Channel
17
+ import { setBlayRuntime } from "./channel/runtime.js";
18
+ import { setBlayClient } from "./channel/state.js";
19
+ import { createBlayChannelPlugin, getGatewayPort, getGatewayBind, getGatewayToken } from "./channel/plugin.js";
20
+ // Tools
21
+ import { createBriefingTool } from "./tools/briefing.js";
22
+ import { createOrgOverviewTool } from "./tools/org.js";
23
+ import { createListUsersTool } from "./tools/users.js";
24
+ import { createListProjectsTool, createGetProjectTool, createCreateProjectTool, createUpdateProjectTool, } from "./tools/projects.js";
25
+ import { createListTasksTool, createGetTaskTool, createCreateTaskTool, createUpdateTaskTool, createDeleteTaskTool, } from "./tools/tasks.js";
26
+ import { createListCommentsTool, createPostCommentTool } from "./tools/comments.js";
27
+ import { createGetContextTool } from "./tools/context.js";
28
+ import { createListActionItemsTool, createCreateActionItemTool, createUpdateActionItemTool, } from "./tools/action-items.js";
29
+ import { createSearchTool } from "./tools/search.js";
30
+ import { createListNotificationsTool, createMarkNotificationReadTool } from "./tools/notifications.js";
31
+ // Instrumentation
32
+ import { instrumentTool, truncate } from "./tools/instrumentation.js";
33
+ import { getCurrentRunTracker } from "./channel/run-tracker.js";
34
+ // Abort
35
+ import { isAborted } from "./channel/abort.js";
36
+ // Telemetry
37
+ import { TelemetryPusher, setTelemetryPusher, getTelemetryPusher } from "./telemetry/pusher.js";
38
+ import { UsageCollector, setUsageCollector } from "./telemetry/usage-collector.js";
39
+ // Webhook (fallback)
40
+ import { createWebhookHandler } from "./webhook/handler.js";
41
+ export default function register(api) {
42
+ const config = (api.pluginConfig ?? {});
43
+ if (!config.apiKey) {
44
+ console.warn("[blay] No apiKey configured — plugin loaded but inactive. Add your API key to plugins.entries.blay-openclaw-plugin.config.apiKey in openclaw.json");
45
+ return;
46
+ }
47
+ const client = new BlayClient(config);
48
+ // Store runtime and client references for channel adapters
49
+ setBlayRuntime(api.runtime);
50
+ setBlayClient(client);
51
+ // ── Register Blay as a native channel (SSE-based) ─────────────────
52
+ const channelPlugin = createBlayChannelPlugin(client, config);
53
+ api.registerChannel({ plugin: channelPlugin });
54
+ // ── Register webhook HTTP route (fallback) ─────────────────────────
55
+ api.registerHttpRoute({
56
+ path: "/plugins/blay/webhook",
57
+ handler: createWebhookHandler(config),
58
+ });
59
+ // ── Register all 21 tools (instrumented for run tracking) ────────
60
+ api.registerTool(instrumentTool(createBriefingTool(client)));
61
+ api.registerTool(instrumentTool(createOrgOverviewTool(client)));
62
+ api.registerTool(instrumentTool(createListUsersTool(client)));
63
+ api.registerTool(instrumentTool(createListProjectsTool(client)));
64
+ api.registerTool(instrumentTool(createGetProjectTool(client)));
65
+ api.registerTool(instrumentTool(createCreateProjectTool(client)));
66
+ api.registerTool(instrumentTool(createUpdateProjectTool(client)));
67
+ api.registerTool(instrumentTool(createListTasksTool(client)));
68
+ api.registerTool(instrumentTool(createGetTaskTool(client)));
69
+ api.registerTool(instrumentTool(createCreateTaskTool(client)));
70
+ api.registerTool(instrumentTool(createUpdateTaskTool(client)));
71
+ api.registerTool(instrumentTool(createDeleteTaskTool(client)));
72
+ api.registerTool(instrumentTool(createListCommentsTool(client)));
73
+ api.registerTool(instrumentTool(createPostCommentTool(client)));
74
+ api.registerTool(instrumentTool(createGetContextTool(client)));
75
+ api.registerTool(instrumentTool(createListActionItemsTool(client)));
76
+ api.registerTool(instrumentTool(createCreateActionItemTool(client)));
77
+ api.registerTool(instrumentTool(createUpdateActionItemTool(client)));
78
+ api.registerTool(instrumentTool(createSearchTool(client)));
79
+ api.registerTool(instrumentTool(createListNotificationsTool(client)));
80
+ api.registerTool(instrumentTool(createMarkNotificationReadTool(client)));
81
+ // ── Telemetry: UsageCollector + TelemetryPusher ──────────────────
82
+ const usageCollector = new UsageCollector();
83
+ setUsageCollector(usageCollector);
84
+ const telemetryPusher = new TelemetryPusher({
85
+ client,
86
+ runtime: api.runtime,
87
+ });
88
+ setTelemetryPusher(telemetryPusher);
89
+ // Subscribe to agent_end hook to capture model usage from messages.
90
+ // The diagnostic_event approach doesn't work due to bundle-level
91
+ // module duplication (the gateway emits to a different listeners set).
92
+ // Instead we inspect the messages array from agent_end for usage metadata.
93
+ api.on("agent_end", (event, _ctx) => {
94
+ if (!event.messages || !Array.isArray(event.messages))
95
+ return;
96
+ // Only process messages added during this run (skip session history)
97
+ const offset = usageCollector.getRunStartMessageCount();
98
+ const newMessages = event.messages.slice(offset);
99
+ let totalInput = 0;
100
+ let totalOutput = 0;
101
+ let totalCost = 0;
102
+ for (const rawMsg of newMessages) {
103
+ const raw = rawMsg;
104
+ // Messages may be wrapped in an envelope { type, id, message: { role, usage, ... } }
105
+ // or may be the inner message directly { role, usage, ... }
106
+ const m = (raw.message && typeof raw.message === "object" ? raw.message : raw);
107
+ if (m.role !== "assistant" || !m.usage || typeof m.usage !== "object")
108
+ continue;
109
+ const usage = m.usage;
110
+ const inputTokens = usage.input ?? usage.input_tokens ?? 0;
111
+ const outputTokens = usage.output ?? usage.output_tokens ?? 0;
112
+ const cacheRead = usage.cacheRead ?? 0;
113
+ const cacheWrite = usage.cacheWrite ?? 0;
114
+ // Use provider's cost.total directly
115
+ const costObj = usage.cost;
116
+ const msgCost = costObj && typeof costObj === "object" ? costObj.total ?? 0 : 0;
117
+ if (inputTokens > 0 || outputTokens > 0 || cacheRead > 0 || cacheWrite > 0) {
118
+ totalInput += inputTokens + cacheRead + cacheWrite;
119
+ totalOutput += outputTokens;
120
+ totalCost += msgCost;
121
+ }
122
+ }
123
+ if (totalInput > 0 || totalOutput > 0) {
124
+ console.log(`[blay] agent_end usage: input=${totalInput}, output=${totalOutput}, cost=${totalCost}`);
125
+ usageCollector.recordUsage({
126
+ inputTokens: totalInput,
127
+ outputTokens: totalOutput,
128
+ cost: totalCost,
129
+ });
130
+ }
131
+ });
132
+ // Subscribe to after_tool_call to capture native/MCP tool calls
133
+ // (blay_* tools are already captured by instrumentTool, so skip those)
134
+ try {
135
+ api.on("after_tool_call", (event) => {
136
+ const toolName = event?.toolName ?? "";
137
+ // Record tool name for config snapshot (all tools, including blay_*)
138
+ const pusher = getTelemetryPusher();
139
+ if (pusher && toolName) {
140
+ pusher.recordToolName(toolName);
141
+ }
142
+ // Run tracking (skip blay_* — already instrumented via instrumentTool)
143
+ const tracker = getCurrentRunTracker();
144
+ if (!tracker)
145
+ return;
146
+ if (toolName.startsWith("blay_"))
147
+ return;
148
+ // OpenClaw may emit after_tool_call twice (once without timing data).
149
+ // Skip the emission that has no durationMs to avoid duplicate records.
150
+ if (event?.durationMs == null)
151
+ return;
152
+ const record = {
153
+ name: toolName,
154
+ params: truncate(event?.params, 2048),
155
+ result: event?.error ? undefined : truncate(event?.result, 1024),
156
+ status: event?.error ? "error" : "success",
157
+ error: event?.error ? truncate(event.error, 1024) : undefined,
158
+ startedAt: event?.durationMs != null ? Date.now() - event.durationMs : Date.now(),
159
+ completedAt: Date.now(),
160
+ durationMs: event?.durationMs ?? 0,
161
+ };
162
+ tracker.recordToolCall(record);
163
+ });
164
+ }
165
+ catch {
166
+ // after_tool_call hook may not be available — that's fine
167
+ }
168
+ // Start telemetry pusher on gateway_start, stop on gateway_stop
169
+ api.on("gateway_start", (_event, _ctx) => {
170
+ telemetryPusher.start();
171
+ });
172
+ api.on("gateway_stop", (_event, _ctx) => {
173
+ telemetryPusher.stop();
174
+ setTelemetryPusher(null);
175
+ setUsageCollector(null);
176
+ });
177
+ // ── Hook: inject Blay context into agent startup ──────────────────
178
+ api.on("before_agent_start", async (event) => {
179
+ // Track message count so agent_end can skip old messages
180
+ usageCollector.setRunStartMessageCount(event?.messages?.length ?? 0);
181
+ return {
182
+ prependContext: "You are connected to the Blay project management platform as a team member. " +
183
+ "Use the blay_* tools to read and write data in Blay (projects, tasks, comments, action items, etc.).\n\n" +
184
+ "IMPORTANT — Cron jobs for Blay: The main session cannot deliver to Blay. " +
185
+ "When scheduling cron jobs from Blay conversations, you MUST use sessionTarget: \"isolated\" " +
186
+ "with payload.kind: \"agentTurn\". Delivery config is auto-inferred from the session — do not set it manually. " +
187
+ "Do NOT use systemEvent/main for Blay; it has no delivery path.",
188
+ };
189
+ });
190
+ // ── Hook: inject loopback gatewayUrl into cron tool calls ─────────
191
+ // When gateway.bind is not "loopback", the cron tool resolves the LAN IP
192
+ // as the gateway URL, which requires device pairing and fails. This hook
193
+ // intercepts cron tool calls and injects gatewayUrl pointing to loopback,
194
+ // which is always allowed by OpenClaw's validation.
195
+ try {
196
+ api.on("before_tool_call", (event, ctx) => {
197
+ const toolName = event?.toolName ?? "";
198
+ console.log(`[blay] before_tool_call fired: tool=${toolName}`);
199
+ // Kill switch: block all tool calls when abort is active
200
+ if (isAborted()) {
201
+ console.log(`[blay] tool call blocked (aborted): ${toolName}`);
202
+ throw new Error("Agent run was aborted by user");
203
+ }
204
+ if (toolName !== "cron")
205
+ return;
206
+ const bind = getGatewayBind();
207
+ if (bind === "loopback")
208
+ return;
209
+ const port = getGatewayPort();
210
+ const params = event?.params ?? {};
211
+ // Only inject if not already set by the agent
212
+ if (!params.gatewayUrl) {
213
+ const token = getGatewayToken();
214
+ const injected = { ...params, gatewayUrl: `ws://127.0.0.1:${port}` };
215
+ if (token) {
216
+ injected.gatewayToken = token;
217
+ }
218
+ console.log(`[blay] before_tool_call: injecting gatewayUrl ws://127.0.0.1:${port} + token for cron tool (bind=${bind})`);
219
+ return { params: injected };
220
+ }
221
+ });
222
+ console.log("[blay] before_tool_call hook registered");
223
+ }
224
+ catch (err) {
225
+ console.warn("[blay] before_tool_call hook registration failed:", err);
226
+ }
227
+ }
228
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * TelemetryPusher — periodically pushes snapshots to the Blay API.
3
+ *
4
+ * Sends heartbeat data every 60 seconds including:
5
+ * - Phase 1: connection status, uptime, run counts
6
+ * - Phase 2: aggregate token usage (via UsageCollector)
7
+ * - Phase 3: cron job snapshots (from cron.json)
8
+ * - Phase 4: agent config snapshots (from runtime config)
9
+ *
10
+ * All pushes are fire-and-forget — telemetry failures must never
11
+ * break agent execution.
12
+ */
13
+ import type { BlayClient } from "../client.js";
14
+ export interface TelemetryPusherOpts {
15
+ client: BlayClient;
16
+ /** Optional: OpenClaw runtime for reading cron.json and config */
17
+ runtime?: any;
18
+ }
19
+ export declare class TelemetryPusher {
20
+ private client;
21
+ private runtime;
22
+ private interval;
23
+ private startedAt;
24
+ private runsCompleted;
25
+ private runsFailed;
26
+ private lastConfigHash;
27
+ private observedTools;
28
+ constructor(opts: TelemetryPusherOpts);
29
+ /** Start periodic telemetry pushes. Called on gateway_start. */
30
+ start(): void;
31
+ /** Stop periodic pushes. Called on gateway_stop. */
32
+ stop(): void;
33
+ /** Record a completed run (called from run tracker). */
34
+ recordRunCompleted(): void;
35
+ /** Record a failed run (called from run tracker). */
36
+ recordRunFailed(): void;
37
+ /** Record an observed tool name (cumulative across the session). */
38
+ recordToolName(name: string): void;
39
+ /** Gather all telemetry data and POST to the API. */
40
+ private pushSnapshot;
41
+ /**
42
+ * Phase 3: Read cron state from OpenClaw's state directory.
43
+ *
44
+ * OpenClaw stores cron data at:
45
+ * <stateDir>/cron/jobs.json — currently configured jobs ({version, jobs: [...]})
46
+ * <stateDir>/cron/runs/*.jsonl — per-job run history (one JSONL per job ID)
47
+ *
48
+ * We read both: configured jobs for current state, and recent run logs
49
+ * for historical visibility (last entry per JSONL = most recent run).
50
+ */
51
+ private readCronJobs;
52
+ /**
53
+ * Phase 4: Read agent config from OpenClaw runtime.
54
+ * Returns null if config hasn't changed since last push.
55
+ */
56
+ private readConfig;
57
+ /**
58
+ * Read markdown files from the agent's workspace directory.
59
+ * Returns filenames and contents. Each file capped at 50KB, total at 200KB.
60
+ */
61
+ private readWorkspaceFiles;
62
+ /**
63
+ * Read SKILL.md files for the given skill names.
64
+ * Searches built-in skills dir and extension skill dirs.
65
+ * Parses YAML frontmatter for description and emoji.
66
+ * Returns a Map of skill name → metadata.
67
+ */
68
+ private readSkillMetadata;
69
+ }
70
+ export declare function setTelemetryPusher(pusher: TelemetryPusher | null): void;
71
+ export declare function getTelemetryPusher(): TelemetryPusher | null;