@unclick/mcp-server 0.3.20 → 0.3.22

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reliability.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliability.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/reliability.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,126 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createDispatchId, createHeartbeat, createReclaimSignal, createTimeBucket, decideStaleLease, } from "../reliability.js";
3
+ describe("reliability helpers", () => {
4
+ it("creates a stable dispatch ID from sorted payload data", () => {
5
+ const first = createDispatchId({
6
+ source: "fishbowl",
7
+ targetAgentId: "bailey",
8
+ taskRef: "chip-123",
9
+ timeBucket: "2026-04-30T11:00:00.000Z",
10
+ payload: { b: 2, a: 1 },
11
+ });
12
+ const second = createDispatchId({
13
+ source: "fishbowl",
14
+ targetAgentId: "bailey",
15
+ taskRef: "chip-123",
16
+ timeBucket: "2026-04-30T11:00:00.000Z",
17
+ payload: { a: 1, b: 2 },
18
+ });
19
+ expect(first).toBe(second);
20
+ expect(first).toMatch(/^dispatch_[a-f0-9]{32}$/);
21
+ });
22
+ it("creates different dispatch IDs when the task changes", () => {
23
+ const first = createDispatchId({
24
+ source: "fishbowl",
25
+ targetAgentId: "bailey",
26
+ taskRef: "chip-123",
27
+ });
28
+ const second = createDispatchId({
29
+ source: "fishbowl",
30
+ targetAgentId: "bailey",
31
+ taskRef: "chip-124",
32
+ });
33
+ expect(first).not.toBe(second);
34
+ });
35
+ it("buckets dispatch time into stable windows", () => {
36
+ expect(createTimeBucket(new Date("2026-04-30T11:00:04.999Z"), 5)).toBe("2026-04-30T11:00:00.000Z");
37
+ expect(createTimeBucket(new Date("2026-04-30T11:00:05.000Z"), 5)).toBe("2026-04-30T11:00:05.000Z");
38
+ });
39
+ it("detects expired leases", () => {
40
+ const decision = decideStaleLease({
41
+ status: "leased",
42
+ leaseExpiresAt: "2026-04-30T11:00:00.000Z",
43
+ }, new Date("2026-04-30T11:00:12.300Z"));
44
+ expect(decision).toEqual({
45
+ isStale: true,
46
+ reason: "lease_expired",
47
+ staleSeconds: 12,
48
+ });
49
+ });
50
+ it("does not mark active or non-leased work as stale", () => {
51
+ expect(decideStaleLease({
52
+ status: "leased",
53
+ leaseExpiresAt: "2026-04-30T11:00:12.000Z",
54
+ }, new Date("2026-04-30T11:00:10.000Z"))).toMatchObject({ isStale: false, reason: "lease_active" });
55
+ expect(decideStaleLease({
56
+ status: "queued",
57
+ leaseExpiresAt: "2026-04-30T11:00:00.000Z",
58
+ }, new Date("2026-04-30T11:00:10.000Z"))).toMatchObject({ isStale: false, reason: "not_leased" });
59
+ });
60
+ it("creates compact heartbeat metadata", () => {
61
+ const heartbeat = createHeartbeat({
62
+ apiKeyHash: "hash_123",
63
+ agentId: "bailey",
64
+ dispatchId: "dispatch_abc",
65
+ state: "working",
66
+ currentTask: "write WakePass PRD",
67
+ nextAction: "open PR",
68
+ etaMinutes: 4,
69
+ createdAt: new Date("2026-04-30T11:00:00.000Z"),
70
+ lastRealActionAt: new Date("2026-04-30T10:59:30.000Z"),
71
+ });
72
+ expect(heartbeat).toEqual({
73
+ apiKeyHash: "hash_123",
74
+ agentId: "bailey",
75
+ dispatchId: "dispatch_abc",
76
+ state: "working",
77
+ currentTask: "write WakePass PRD",
78
+ nextAction: "open PR",
79
+ etaMinutes: 4,
80
+ createdAt: "2026-04-30T11:00:00.000Z",
81
+ lastRealActionAt: "2026-04-30T10:59:30.000Z",
82
+ });
83
+ });
84
+ it("marks missing-ack handoffs as a WakePass reliability miss", () => {
85
+ const signal = createReclaimSignal({
86
+ dispatchId: "dispatch_ack",
87
+ source: "fishbowl",
88
+ targetAgentId: "plex",
89
+ taskRef: "todo-123",
90
+ payload: { ack_required: true, handoff_message_id: "msg-abc" },
91
+ }, 95);
92
+ expect(signal).toEqual({
93
+ action: "handoff_ack_missing",
94
+ summary: "WakePass reliability miss: no ACK arrived before reclaim for plex",
95
+ payload: {
96
+ dispatch_id: "dispatch_ack",
97
+ source: "fishbowl",
98
+ target_agent_id: "plex",
99
+ task_ref: "todo-123",
100
+ stale_seconds: 95,
101
+ ack_required: true,
102
+ handoff_message_id: "msg-abc",
103
+ },
104
+ });
105
+ });
106
+ it("marks non-ack reclaim as a generic stale dispatch", () => {
107
+ const signal = createReclaimSignal({
108
+ dispatchId: "dispatch_generic",
109
+ source: "connectors",
110
+ targetAgentId: "bailey",
111
+ taskRef: "conn-42",
112
+ payload: { route: "oauth-health" },
113
+ }, 12);
114
+ expect(signal.action).toBe("stale_dispatch_reclaimed");
115
+ expect(signal.summary).toBe("Reclaimed stale connectors dispatch for bailey");
116
+ expect(signal.payload).toMatchObject({
117
+ dispatch_id: "dispatch_generic",
118
+ source: "connectors",
119
+ target_agent_id: "bailey",
120
+ task_ref: "conn-42",
121
+ stale_seconds: 12,
122
+ route: "oauth-health",
123
+ });
124
+ });
125
+ });
126
+ //# sourceMappingURL=reliability.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliability.test.js","sourceRoot":"","sources":["../../src/__tests__/reliability.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ export type DispatchSource = "fishbowl" | "connectors" | "wakepass" | "testpass" | "uxpass" | "flowpass" | "securitypass" | "manual";
2
+ export type DispatchStatus = "queued" | "leased" | "completed" | "failed" | "stale" | "cancelled";
3
+ export type HeartbeatState = "idle" | "received" | "accepted" | "working" | "blocked" | "completed";
4
+ export interface AgentDispatch {
5
+ apiKeyHash: string;
6
+ dispatchId: string;
7
+ source: DispatchSource;
8
+ targetAgentId: string;
9
+ taskRef?: string;
10
+ status: DispatchStatus;
11
+ leaseOwner?: string;
12
+ leaseExpiresAt?: string;
13
+ lastRealActionAt?: string;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ payload?: Record<string, unknown>;
17
+ }
18
+ export interface AgentHeartbeat {
19
+ apiKeyHash: string;
20
+ agentId: string;
21
+ dispatchId?: string;
22
+ state: HeartbeatState;
23
+ currentTask?: string;
24
+ nextAction?: string;
25
+ etaMinutes?: number;
26
+ blocker?: string;
27
+ lastRealActionAt?: string;
28
+ createdAt: string;
29
+ }
30
+ export interface DispatchIdInput {
31
+ source: DispatchSource;
32
+ targetAgentId: string;
33
+ taskRef?: string;
34
+ promptHash?: string;
35
+ timeBucket?: string;
36
+ payload?: Record<string, unknown>;
37
+ }
38
+ export interface ReclaimSignalDescriptor {
39
+ action: "stale_dispatch_reclaimed" | "handoff_ack_missing";
40
+ summary: string;
41
+ payload: Record<string, unknown>;
42
+ }
43
+ export interface StaleLeaseInput {
44
+ status: DispatchStatus;
45
+ leaseExpiresAt?: string | null;
46
+ lastRealActionAt?: string | null;
47
+ }
48
+ export interface StaleLeaseDecision {
49
+ isStale: boolean;
50
+ reason: "not_leased" | "missing_lease_expiry" | "lease_active" | "lease_expired";
51
+ staleSeconds: number;
52
+ }
53
+ export declare function createDispatchId(input: DispatchIdInput): string;
54
+ export declare function createTimeBucket(date: Date, bucketSeconds?: number): string;
55
+ export declare function decideStaleLease(input: StaleLeaseInput, now?: Date): StaleLeaseDecision;
56
+ export declare function createHeartbeat(params: {
57
+ apiKeyHash: string;
58
+ agentId: string;
59
+ state: HeartbeatState;
60
+ createdAt?: Date;
61
+ dispatchId?: string;
62
+ currentTask?: string;
63
+ nextAction?: string;
64
+ etaMinutes?: number;
65
+ blocker?: string;
66
+ lastRealActionAt?: Date;
67
+ }): AgentHeartbeat;
68
+ export declare function createReclaimSignal(dispatch: Pick<AgentDispatch, "dispatchId" | "source" | "targetAgentId" | "taskRef" | "payload">, staleSeconds: number): ReclaimSignalDescriptor;
69
+ //# sourceMappingURL=reliability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliability.d.ts","sourceRoot":"","sources":["../src/reliability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { createHash } from "node:crypto";
2
+ export function createDispatchId(input) {
3
+ const hash = createHash("sha256")
4
+ .update(stableStringify(input))
5
+ .digest("hex")
6
+ .slice(0, 32);
7
+ return `dispatch_${hash}`;
8
+ }
9
+ export function createTimeBucket(date, bucketSeconds = 5) {
10
+ if (!Number.isFinite(bucketSeconds) || bucketSeconds <= 0) {
11
+ throw new Error("bucketSeconds must be a positive number");
12
+ }
13
+ const bucketMs = bucketSeconds * 1000;
14
+ const bucketStart = Math.floor(date.getTime() / bucketMs) * bucketMs;
15
+ return new Date(bucketStart).toISOString();
16
+ }
17
+ export function decideStaleLease(input, now = new Date()) {
18
+ if (input.status !== "leased") {
19
+ return { isStale: false, reason: "not_leased", staleSeconds: 0 };
20
+ }
21
+ if (!input.leaseExpiresAt) {
22
+ return { isStale: false, reason: "missing_lease_expiry", staleSeconds: 0 };
23
+ }
24
+ const leaseExpiresAtMs = Date.parse(input.leaseExpiresAt);
25
+ if (Number.isNaN(leaseExpiresAtMs)) {
26
+ return { isStale: false, reason: "missing_lease_expiry", staleSeconds: 0 };
27
+ }
28
+ const staleMs = now.getTime() - leaseExpiresAtMs;
29
+ if (staleMs <= 0) {
30
+ return { isStale: false, reason: "lease_active", staleSeconds: 0 };
31
+ }
32
+ return {
33
+ isStale: true,
34
+ reason: "lease_expired",
35
+ staleSeconds: Math.floor(staleMs / 1000),
36
+ };
37
+ }
38
+ export function createHeartbeat(params) {
39
+ const heartbeat = {
40
+ apiKeyHash: params.apiKeyHash,
41
+ agentId: params.agentId,
42
+ state: params.state,
43
+ createdAt: (params.createdAt ?? new Date()).toISOString(),
44
+ };
45
+ if (params.dispatchId)
46
+ heartbeat.dispatchId = params.dispatchId;
47
+ if (params.currentTask)
48
+ heartbeat.currentTask = params.currentTask;
49
+ if (params.nextAction)
50
+ heartbeat.nextAction = params.nextAction;
51
+ if (typeof params.etaMinutes === "number")
52
+ heartbeat.etaMinutes = params.etaMinutes;
53
+ if (params.blocker)
54
+ heartbeat.blocker = params.blocker;
55
+ if (params.lastRealActionAt) {
56
+ heartbeat.lastRealActionAt = params.lastRealActionAt.toISOString();
57
+ }
58
+ return heartbeat;
59
+ }
60
+ export function createReclaimSignal(dispatch, staleSeconds) {
61
+ const payload = dispatch.payload ?? {};
62
+ const expectsAck = payload.ack_required === true ||
63
+ payload.require_ack === true ||
64
+ typeof payload.handoff_message_id === "string" ||
65
+ typeof payload.handoff_thread_id === "string";
66
+ if (expectsAck) {
67
+ return {
68
+ action: "handoff_ack_missing",
69
+ summary: `WakePass reliability miss: no ACK arrived before reclaim for ${dispatch.targetAgentId}`,
70
+ payload: {
71
+ dispatch_id: dispatch.dispatchId,
72
+ source: dispatch.source,
73
+ target_agent_id: dispatch.targetAgentId,
74
+ task_ref: dispatch.taskRef ?? null,
75
+ stale_seconds: staleSeconds,
76
+ ...payload,
77
+ },
78
+ };
79
+ }
80
+ return {
81
+ action: "stale_dispatch_reclaimed",
82
+ summary: `Reclaimed stale ${dispatch.source} dispatch for ${dispatch.targetAgentId}`,
83
+ payload: {
84
+ dispatch_id: dispatch.dispatchId,
85
+ source: dispatch.source,
86
+ target_agent_id: dispatch.targetAgentId,
87
+ task_ref: dispatch.taskRef ?? null,
88
+ stale_seconds: staleSeconds,
89
+ ...payload,
90
+ },
91
+ };
92
+ }
93
+ function stableStringify(value) {
94
+ if (value === null || typeof value !== "object") {
95
+ return JSON.stringify(value);
96
+ }
97
+ if (Array.isArray(value)) {
98
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
99
+ }
100
+ const record = value;
101
+ const keys = Object.keys(record).sort();
102
+ return `{${keys
103
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
104
+ .join(",")}}`;
105
+ }
106
+ //# sourceMappingURL=reliability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliability.js","sourceRoot":"","sources":["../src/reliability.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unclick/mcp-server",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "mcpName": "io.github.malamutemayhem/unclick-mcp-server",
5
5
  "description": "MCP server for the UnClick tool marketplace — lets AI agents discover and use every UnClick tool",
6
6
  "type": "module",