opencode-heartbeat-approval 0.1.0 → 0.2.0
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/dist/index.js +25 -7
- package/dist/plugin.js +25 -7
- package/package.json +2 -2
- package/src/plugin.ts +30 -6
package/dist/index.js
CHANGED
|
@@ -12386,31 +12386,44 @@ function sleep(ms, signal) {
|
|
|
12386
12386
|
var HeartbeatApprovalPlugin = async (_input) => {
|
|
12387
12387
|
return {
|
|
12388
12388
|
tool: {
|
|
12389
|
-
|
|
12390
|
-
description: `Request human
|
|
12389
|
+
request_human_input: tool({
|
|
12390
|
+
description: `Request human input before proceeding \u2014 either approval (permission to act) or assistance (human help needed).
|
|
12391
12391
|
|
|
12392
|
-
Call this tool when you need explicit human confirmation
|
|
12392
|
+
Call this tool when you need explicit human confirmation or help. Use type "approval" for actions needing permission (publishing, trading, irreversible changes). Use type "assistance" when you need the human to do something (enter a captcha, provide credentials, perform a physical action).
|
|
12393
12393
|
|
|
12394
|
-
|
|
12394
|
+
The tool sends a Feishu interactive card and blocks until the human responds (or the deadline expires). Only call this when genuinely necessary.
|
|
12395
12395
|
|
|
12396
|
-
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", response?: string, approval_id: string | null }`,
|
|
12396
|
+
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", type: "approval" | "assistance", response?: string, approval_id: string | null }`,
|
|
12397
12397
|
args: {
|
|
12398
|
-
prompt: tool.schema.string().describe("What you need
|
|
12398
|
+
prompt: tool.schema.string().describe("What you need from the human \u2014 be specific and concise"),
|
|
12399
|
+
type: tool.schema.string().optional().describe("Type of request: 'approval' (need permission) or 'assistance' (need human help). Default: 'approval'"),
|
|
12399
12400
|
artifacts: tool.schema.string().optional().describe("Comma-separated list of relevant file paths or artifact names"),
|
|
12400
12401
|
deadline_hours: tool.schema.number().optional().describe("Hours before the request expires (default: 24, max: 168)")
|
|
12401
12402
|
},
|
|
12402
12403
|
async execute(args, ctx) {
|
|
12403
12404
|
const artifacts = args.artifacts ? args.artifacts.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
12405
|
+
const validTypes = ["approval", "assistance"];
|
|
12406
|
+
if (args.type !== undefined && !validTypes.includes(args.type)) {
|
|
12407
|
+
return JSON.stringify({
|
|
12408
|
+
status: "error",
|
|
12409
|
+
type: null,
|
|
12410
|
+
error: `Invalid type "${args.type}". Must be "approval" or "assistance".`,
|
|
12411
|
+
approval_id: null
|
|
12412
|
+
});
|
|
12413
|
+
}
|
|
12414
|
+
const requestType = args.type === "assistance" ? "assistance" : "approval";
|
|
12404
12415
|
let gateResult;
|
|
12405
12416
|
try {
|
|
12406
12417
|
gateResult = await createGate({
|
|
12407
12418
|
prompt: args.prompt,
|
|
12408
12419
|
artifacts,
|
|
12409
|
-
deadline_hours: args.deadline_hours
|
|
12420
|
+
deadline_hours: args.deadline_hours,
|
|
12421
|
+
type: requestType
|
|
12410
12422
|
});
|
|
12411
12423
|
} catch (err) {
|
|
12412
12424
|
return JSON.stringify({
|
|
12413
12425
|
status: "unavailable",
|
|
12426
|
+
type: requestType,
|
|
12414
12427
|
error: err instanceof Error ? err.message : String(err),
|
|
12415
12428
|
approval_id: null
|
|
12416
12429
|
});
|
|
@@ -12418,6 +12431,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12418
12431
|
if ("error" in gateResult) {
|
|
12419
12432
|
return JSON.stringify({
|
|
12420
12433
|
status: "conflict",
|
|
12434
|
+
type: requestType,
|
|
12421
12435
|
error: gateResult.message,
|
|
12422
12436
|
existing_approval_id: gateResult.existing_approval_id,
|
|
12423
12437
|
approval_id: null
|
|
@@ -12428,6 +12442,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12428
12442
|
if (ctx.abort?.aborted) {
|
|
12429
12443
|
return JSON.stringify({
|
|
12430
12444
|
status: "cancelled",
|
|
12445
|
+
type: requestType,
|
|
12431
12446
|
response: null,
|
|
12432
12447
|
approval_id: approvalId
|
|
12433
12448
|
});
|
|
@@ -12443,6 +12458,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12443
12458
|
if (poll.status === "approved") {
|
|
12444
12459
|
return JSON.stringify({
|
|
12445
12460
|
status: "approved",
|
|
12461
|
+
type: requestType,
|
|
12446
12462
|
response: poll.response ?? null,
|
|
12447
12463
|
approval_id: approvalId
|
|
12448
12464
|
});
|
|
@@ -12450,6 +12466,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12450
12466
|
if (poll.status === "rejected") {
|
|
12451
12467
|
return JSON.stringify({
|
|
12452
12468
|
status: "rejected",
|
|
12469
|
+
type: requestType,
|
|
12453
12470
|
response: poll.response ?? null,
|
|
12454
12471
|
approval_id: approvalId
|
|
12455
12472
|
});
|
|
@@ -12457,6 +12474,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12457
12474
|
if (poll.status === "expired") {
|
|
12458
12475
|
return JSON.stringify({
|
|
12459
12476
|
status: "expired",
|
|
12477
|
+
type: requestType,
|
|
12460
12478
|
response: null,
|
|
12461
12479
|
approval_id: approvalId
|
|
12462
12480
|
});
|
package/dist/plugin.js
CHANGED
|
@@ -12386,31 +12386,44 @@ function sleep(ms, signal) {
|
|
|
12386
12386
|
var HeartbeatApprovalPlugin = async (_input) => {
|
|
12387
12387
|
return {
|
|
12388
12388
|
tool: {
|
|
12389
|
-
|
|
12390
|
-
description: `Request human
|
|
12389
|
+
request_human_input: tool({
|
|
12390
|
+
description: `Request human input before proceeding \u2014 either approval (permission to act) or assistance (human help needed).
|
|
12391
12391
|
|
|
12392
|
-
Call this tool when you need explicit human confirmation
|
|
12392
|
+
Call this tool when you need explicit human confirmation or help. Use type "approval" for actions needing permission (publishing, trading, irreversible changes). Use type "assistance" when you need the human to do something (enter a captcha, provide credentials, perform a physical action).
|
|
12393
12393
|
|
|
12394
|
-
|
|
12394
|
+
The tool sends a Feishu interactive card and blocks until the human responds (or the deadline expires). Only call this when genuinely necessary.
|
|
12395
12395
|
|
|
12396
|
-
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", response?: string, approval_id: string | null }`,
|
|
12396
|
+
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", type: "approval" | "assistance", response?: string, approval_id: string | null }`,
|
|
12397
12397
|
args: {
|
|
12398
|
-
prompt: tool.schema.string().describe("What you need
|
|
12398
|
+
prompt: tool.schema.string().describe("What you need from the human \u2014 be specific and concise"),
|
|
12399
|
+
type: tool.schema.string().optional().describe("Type of request: 'approval' (need permission) or 'assistance' (need human help). Default: 'approval'"),
|
|
12399
12400
|
artifacts: tool.schema.string().optional().describe("Comma-separated list of relevant file paths or artifact names"),
|
|
12400
12401
|
deadline_hours: tool.schema.number().optional().describe("Hours before the request expires (default: 24, max: 168)")
|
|
12401
12402
|
},
|
|
12402
12403
|
async execute(args, ctx) {
|
|
12403
12404
|
const artifacts = args.artifacts ? args.artifacts.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
12405
|
+
const validTypes = ["approval", "assistance"];
|
|
12406
|
+
if (args.type !== undefined && !validTypes.includes(args.type)) {
|
|
12407
|
+
return JSON.stringify({
|
|
12408
|
+
status: "error",
|
|
12409
|
+
type: null,
|
|
12410
|
+
error: `Invalid type "${args.type}". Must be "approval" or "assistance".`,
|
|
12411
|
+
approval_id: null
|
|
12412
|
+
});
|
|
12413
|
+
}
|
|
12414
|
+
const requestType = args.type === "assistance" ? "assistance" : "approval";
|
|
12404
12415
|
let gateResult;
|
|
12405
12416
|
try {
|
|
12406
12417
|
gateResult = await createGate({
|
|
12407
12418
|
prompt: args.prompt,
|
|
12408
12419
|
artifacts,
|
|
12409
|
-
deadline_hours: args.deadline_hours
|
|
12420
|
+
deadline_hours: args.deadline_hours,
|
|
12421
|
+
type: requestType
|
|
12410
12422
|
});
|
|
12411
12423
|
} catch (err) {
|
|
12412
12424
|
return JSON.stringify({
|
|
12413
12425
|
status: "unavailable",
|
|
12426
|
+
type: requestType,
|
|
12414
12427
|
error: err instanceof Error ? err.message : String(err),
|
|
12415
12428
|
approval_id: null
|
|
12416
12429
|
});
|
|
@@ -12418,6 +12431,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12418
12431
|
if ("error" in gateResult) {
|
|
12419
12432
|
return JSON.stringify({
|
|
12420
12433
|
status: "conflict",
|
|
12434
|
+
type: requestType,
|
|
12421
12435
|
error: gateResult.message,
|
|
12422
12436
|
existing_approval_id: gateResult.existing_approval_id,
|
|
12423
12437
|
approval_id: null
|
|
@@ -12428,6 +12442,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12428
12442
|
if (ctx.abort?.aborted) {
|
|
12429
12443
|
return JSON.stringify({
|
|
12430
12444
|
status: "cancelled",
|
|
12445
|
+
type: requestType,
|
|
12431
12446
|
response: null,
|
|
12432
12447
|
approval_id: approvalId
|
|
12433
12448
|
});
|
|
@@ -12443,6 +12458,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12443
12458
|
if (poll.status === "approved") {
|
|
12444
12459
|
return JSON.stringify({
|
|
12445
12460
|
status: "approved",
|
|
12461
|
+
type: requestType,
|
|
12446
12462
|
response: poll.response ?? null,
|
|
12447
12463
|
approval_id: approvalId
|
|
12448
12464
|
});
|
|
@@ -12450,6 +12466,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12450
12466
|
if (poll.status === "rejected") {
|
|
12451
12467
|
return JSON.stringify({
|
|
12452
12468
|
status: "rejected",
|
|
12469
|
+
type: requestType,
|
|
12453
12470
|
response: poll.response ?? null,
|
|
12454
12471
|
approval_id: approvalId
|
|
12455
12472
|
});
|
|
@@ -12457,6 +12474,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
12457
12474
|
if (poll.status === "expired") {
|
|
12458
12475
|
return JSON.stringify({
|
|
12459
12476
|
status: "expired",
|
|
12477
|
+
type: requestType,
|
|
12460
12478
|
response: null,
|
|
12461
12479
|
approval_id: approvalId
|
|
12462
12480
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-heartbeat-approval",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenCode plugin providing
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenCode plugin providing request_human_input MCP tool for Heartbeat pipeline (approval + assistance)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
package/src/plugin.ts
CHANGED
|
@@ -8,12 +8,14 @@ interface CreateResponse {
|
|
|
8
8
|
approval_id: string;
|
|
9
9
|
status: string;
|
|
10
10
|
created: boolean;
|
|
11
|
+
type?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
interface PollResponse {
|
|
14
15
|
approval_id: string | null;
|
|
15
16
|
status: string;
|
|
16
17
|
response?: string;
|
|
18
|
+
type?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
function getRunnerUrl(): string {
|
|
@@ -40,6 +42,7 @@ async function createGate(params: {
|
|
|
40
42
|
artifacts?: string[];
|
|
41
43
|
deadline_hours?: number;
|
|
42
44
|
phase?: string;
|
|
45
|
+
type?: string;
|
|
43
46
|
}): Promise<CreateResponse | ConflictResponse> {
|
|
44
47
|
const resp = await fetch(`${getRunnerUrl()}/api/approval`, {
|
|
45
48
|
method: "POST",
|
|
@@ -79,18 +82,22 @@ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
|
79
82
|
export const HeartbeatApprovalPlugin = async (_input: PluginInput) => {
|
|
80
83
|
return {
|
|
81
84
|
tool: {
|
|
82
|
-
|
|
83
|
-
description: `Request human
|
|
85
|
+
request_human_input: tool({
|
|
86
|
+
description: `Request human input before proceeding — either approval (permission to act) or assistance (human help needed).
|
|
84
87
|
|
|
85
|
-
Call this tool when you need explicit human confirmation
|
|
88
|
+
Call this tool when you need explicit human confirmation or help. Use type "approval" for actions needing permission (publishing, trading, irreversible changes). Use type "assistance" when you need the human to do something (enter a captcha, provide credentials, perform a physical action).
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
The tool sends a Feishu interactive card and blocks until the human responds (or the deadline expires). Only call this when genuinely necessary.
|
|
88
91
|
|
|
89
|
-
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", response?: string, approval_id: string | null }`,
|
|
92
|
+
Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancelled" | "conflict", type: "approval" | "assistance", response?: string, approval_id: string | null }`,
|
|
90
93
|
args: {
|
|
91
94
|
prompt: tool.schema
|
|
92
95
|
.string()
|
|
93
|
-
.describe("What you need
|
|
96
|
+
.describe("What you need from the human — be specific and concise"),
|
|
97
|
+
type: tool.schema
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe("Type of request: 'approval' (need permission) or 'assistance' (need human help). Default: 'approval'"),
|
|
94
101
|
artifacts: tool.schema
|
|
95
102
|
.string()
|
|
96
103
|
.optional()
|
|
@@ -104,6 +111,16 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
104
111
|
const artifacts = args.artifacts
|
|
105
112
|
? args.artifacts.split(",").map((s: string) => s.trim()).filter(Boolean)
|
|
106
113
|
: undefined;
|
|
114
|
+
const validTypes = ["approval", "assistance"];
|
|
115
|
+
if (args.type !== undefined && !validTypes.includes(args.type)) {
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
status: "error",
|
|
118
|
+
type: null,
|
|
119
|
+
error: `Invalid type "${args.type}". Must be "approval" or "assistance".`,
|
|
120
|
+
approval_id: null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const requestType = (args.type === "assistance" ? "assistance" : "approval") as "approval" | "assistance";
|
|
107
124
|
|
|
108
125
|
let gateResult: CreateResponse | ConflictResponse;
|
|
109
126
|
try {
|
|
@@ -111,10 +128,12 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
111
128
|
prompt: args.prompt,
|
|
112
129
|
artifacts,
|
|
113
130
|
deadline_hours: args.deadline_hours,
|
|
131
|
+
type: requestType,
|
|
114
132
|
});
|
|
115
133
|
} catch (err) {
|
|
116
134
|
return JSON.stringify({
|
|
117
135
|
status: "unavailable",
|
|
136
|
+
type: requestType,
|
|
118
137
|
error: err instanceof Error ? err.message : String(err),
|
|
119
138
|
approval_id: null,
|
|
120
139
|
});
|
|
@@ -123,6 +142,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
123
142
|
if ("error" in gateResult) {
|
|
124
143
|
return JSON.stringify({
|
|
125
144
|
status: "conflict",
|
|
145
|
+
type: requestType,
|
|
126
146
|
error: gateResult.message,
|
|
127
147
|
existing_approval_id: gateResult.existing_approval_id,
|
|
128
148
|
approval_id: null,
|
|
@@ -135,6 +155,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
135
155
|
if (ctx.abort?.aborted) {
|
|
136
156
|
return JSON.stringify({
|
|
137
157
|
status: "cancelled",
|
|
158
|
+
type: requestType,
|
|
138
159
|
response: null,
|
|
139
160
|
approval_id: approvalId,
|
|
140
161
|
});
|
|
@@ -153,6 +174,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
153
174
|
if (poll.status === "approved") {
|
|
154
175
|
return JSON.stringify({
|
|
155
176
|
status: "approved",
|
|
177
|
+
type: requestType,
|
|
156
178
|
response: poll.response ?? null,
|
|
157
179
|
approval_id: approvalId,
|
|
158
180
|
});
|
|
@@ -161,6 +183,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
161
183
|
if (poll.status === "rejected") {
|
|
162
184
|
return JSON.stringify({
|
|
163
185
|
status: "rejected",
|
|
186
|
+
type: requestType,
|
|
164
187
|
response: poll.response ?? null,
|
|
165
188
|
approval_id: approvalId,
|
|
166
189
|
});
|
|
@@ -169,6 +192,7 @@ Returns: { status: "approved" | "rejected" | "expired" | "unavailable" | "cancel
|
|
|
169
192
|
if (poll.status === "expired") {
|
|
170
193
|
return JSON.stringify({
|
|
171
194
|
status: "expired",
|
|
195
|
+
type: requestType,
|
|
172
196
|
response: null,
|
|
173
197
|
approval_id: approvalId,
|
|
174
198
|
});
|