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 CHANGED
@@ -12386,31 +12386,44 @@ function sleep(ms, signal) {
12386
12386
  var HeartbeatApprovalPlugin = async (_input) => {
12387
12387
  return {
12388
12388
  tool: {
12389
- request_human_approval: tool({
12390
- description: `Request human approval before proceeding with a significant action.
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 \u2014 for example before publishing content, executing a trade, or making an irreversible change. The tool sends a Feishu interactive card with approve/reject buttons and blocks until the human responds (or the deadline expires).
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
- Only call this when genuinely necessary. Do not use it as a routine step.
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 human approval for \u2014 be specific and concise"),
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
- request_human_approval: tool({
12390
- description: `Request human approval before proceeding with a significant action.
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 \u2014 for example before publishing content, executing a trade, or making an irreversible change. The tool sends a Feishu interactive card with approve/reject buttons and blocks until the human responds (or the deadline expires).
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
- Only call this when genuinely necessary. Do not use it as a routine step.
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 human approval for \u2014 be specific and concise"),
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.1.0",
4
- "description": "OpenCode plugin providing request_human_approval MCP tool for Heartbeat pipeline",
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
- request_human_approval: tool({
83
- description: `Request human approval before proceeding with a significant action.
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 for example before publishing content, executing a trade, or making an irreversible change. The tool sends a Feishu interactive card with approve/reject buttons and blocks until the human responds (or the deadline expires).
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
- Only call this when genuinely necessary. Do not use it as a routine step.
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 human approval for — be specific and concise"),
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
  });