@acnlabs/paperclip-plugin-acn 0.1.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.
@@ -0,0 +1,287 @@
1
+ // src/ui/index.tsx
2
+ import { useState } from "react";
3
+ import {
4
+ usePluginAction,
5
+ usePluginData
6
+ } from "@paperclipai/plugin-sdk/ui";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ var styles = {
9
+ container: {
10
+ padding: "16px",
11
+ fontFamily: "inherit",
12
+ fontSize: "13px",
13
+ color: "var(--color-fg-default, #1a1a1a)"
14
+ },
15
+ row: {
16
+ display: "flex",
17
+ gap: "8px",
18
+ alignItems: "baseline",
19
+ marginBottom: "6px"
20
+ },
21
+ label: {
22
+ fontWeight: 600,
23
+ color: "var(--color-fg-muted, #666)",
24
+ minWidth: "100px",
25
+ flexShrink: 0
26
+ },
27
+ badge: (color) => ({
28
+ display: "inline-block",
29
+ padding: "2px 8px",
30
+ borderRadius: "12px",
31
+ fontSize: "11px",
32
+ fontWeight: 600,
33
+ background: color,
34
+ color: "#fff",
35
+ textTransform: "uppercase",
36
+ letterSpacing: "0.04em"
37
+ }),
38
+ section: {
39
+ marginTop: "16px",
40
+ borderTop: "1px solid var(--color-border-default, #e5e5e5)",
41
+ paddingTop: "12px"
42
+ },
43
+ sectionTitle: {
44
+ fontWeight: 700,
45
+ marginBottom: "8px",
46
+ fontSize: "12px",
47
+ textTransform: "uppercase",
48
+ letterSpacing: "0.06em",
49
+ color: "var(--color-fg-muted, #888)"
50
+ },
51
+ participationCard: {
52
+ background: "var(--color-bg-subtle, #f6f6f6)",
53
+ borderRadius: "6px",
54
+ padding: "10px 12px",
55
+ marginBottom: "8px"
56
+ },
57
+ pre: {
58
+ background: "var(--color-bg-subtle, #f0f0f0)",
59
+ borderRadius: "4px",
60
+ padding: "8px",
61
+ fontSize: "12px",
62
+ overflowX: "auto",
63
+ whiteSpace: "pre-wrap",
64
+ wordBreak: "break-word",
65
+ marginTop: "6px"
66
+ },
67
+ actionRow: {
68
+ display: "flex",
69
+ gap: "8px",
70
+ marginTop: "16px"
71
+ },
72
+ btn: (variant) => ({
73
+ padding: "6px 16px",
74
+ borderRadius: "6px",
75
+ border: "none",
76
+ cursor: "pointer",
77
+ fontWeight: 600,
78
+ fontSize: "13px",
79
+ background: variant === "approve" ? "#16a34a" : variant === "reject" ? "#dc2626" : "var(--color-bg-subtle, #e5e5e5)",
80
+ color: variant === "neutral" ? "var(--color-fg-default, #333)" : "#fff",
81
+ opacity: 1,
82
+ transition: "opacity 0.15s"
83
+ }),
84
+ textarea: {
85
+ width: "100%",
86
+ minHeight: "64px",
87
+ borderRadius: "6px",
88
+ border: "1px solid var(--color-border-default, #ccc)",
89
+ padding: "6px 8px",
90
+ fontSize: "12px",
91
+ fontFamily: "inherit",
92
+ resize: "vertical",
93
+ boxSizing: "border-box",
94
+ marginTop: "8px"
95
+ },
96
+ muted: {
97
+ color: "var(--color-fg-muted, #888)",
98
+ fontSize: "12px"
99
+ },
100
+ mono: {
101
+ fontFamily: "monospace",
102
+ fontSize: "11px",
103
+ background: "var(--color-bg-subtle, #f0f0f0)",
104
+ padding: "1px 5px",
105
+ borderRadius: "3px"
106
+ }
107
+ };
108
+ var STATUS_COLORS = {
109
+ open: "#2563eb",
110
+ in_progress: "#d97706",
111
+ in_review: "#7c3aed",
112
+ completed: "#16a34a",
113
+ cancelled: "#6b7280",
114
+ rejected: "#dc2626",
115
+ submitted: "#7c3aed",
116
+ accepted: "#d97706"
117
+ };
118
+ function StatusBadge({ status }) {
119
+ const color = STATUS_COLORS[status] ?? "#6b7280";
120
+ return /* @__PURE__ */ jsx("span", { style: styles.badge(color), children: status.replace(/_/g, " ") });
121
+ }
122
+ function ParticipationItem({
123
+ p
124
+ }) {
125
+ const [expanded, setExpanded] = useState(false);
126
+ const hasSubmission = Boolean(p.submission_content);
127
+ return /* @__PURE__ */ jsxs("div", { style: styles.participationCard, children: [
128
+ /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
129
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Agent" }),
130
+ /* @__PURE__ */ jsx("span", { style: styles.mono, children: p.agent_id })
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
133
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Status" }),
134
+ /* @__PURE__ */ jsx(StatusBadge, { status: p.status }),
135
+ p.resubmit_count > 0 && /* @__PURE__ */ jsxs("span", { style: styles.muted, children: [
136
+ "(",
137
+ p.resubmit_count,
138
+ " resubmit",
139
+ p.resubmit_count > 1 ? "s" : "",
140
+ ")"
141
+ ] })
142
+ ] }),
143
+ p.submitted_at && /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
144
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Submitted" }),
145
+ /* @__PURE__ */ jsx("span", { style: styles.muted, children: new Date(p.submitted_at).toLocaleString() })
146
+ ] }),
147
+ hasSubmission && /* @__PURE__ */ jsxs(Fragment, { children: [
148
+ /* @__PURE__ */ jsx(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ style: { ...styles.btn("neutral"), marginTop: "6px", fontSize: "11px", padding: "3px 10px" },
153
+ onClick: () => setExpanded((v) => !v),
154
+ children: expanded ? "Hide submission" : "Show submission"
155
+ }
156
+ ),
157
+ expanded && /* @__PURE__ */ jsx("pre", { style: styles.pre, children: p.submission_content })
158
+ ] })
159
+ ] });
160
+ }
161
+ function ReviewPanel({
162
+ taskId,
163
+ onDone
164
+ }) {
165
+ const [feedback, setFeedback] = useState("");
166
+ const [pending, setPending] = useState(null);
167
+ const [error, setError] = useState(null);
168
+ const review = usePluginAction("acn-review");
169
+ async function handleReview(approved) {
170
+ const action = approved ? "approve" : "reject";
171
+ setPending(action);
172
+ setError(null);
173
+ try {
174
+ await review({ taskId, approved, feedback: feedback.trim() || void 0 });
175
+ onDone();
176
+ } catch (err) {
177
+ setError(err instanceof Error ? err.message : "Review failed");
178
+ } finally {
179
+ setPending(null);
180
+ }
181
+ }
182
+ return /* @__PURE__ */ jsxs("div", { style: styles.section, children: [
183
+ /* @__PURE__ */ jsx("div", { style: styles.sectionTitle, children: "Review Submission" }),
184
+ /* @__PURE__ */ jsx(
185
+ "textarea",
186
+ {
187
+ style: styles.textarea,
188
+ placeholder: "Optional feedback\u2026",
189
+ value: feedback,
190
+ onChange: (e) => setFeedback(e.target.value),
191
+ disabled: pending !== null
192
+ }
193
+ ),
194
+ error && /* @__PURE__ */ jsx("div", { style: { color: "#dc2626", fontSize: "12px", marginTop: "4px" }, children: error }),
195
+ /* @__PURE__ */ jsxs("div", { style: styles.actionRow, children: [
196
+ /* @__PURE__ */ jsx(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ style: { ...styles.btn("approve"), opacity: pending ? 0.6 : 1 },
201
+ disabled: pending !== null,
202
+ onClick: () => void handleReview(true),
203
+ children: pending === "approve" ? "Approving\u2026" : "Approve"
204
+ }
205
+ ),
206
+ /* @__PURE__ */ jsx(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ style: { ...styles.btn("reject"), opacity: pending ? 0.6 : 1 },
211
+ disabled: pending !== null,
212
+ onClick: () => void handleReview(false),
213
+ children: pending === "reject" ? "Rejecting\u2026" : "Reject"
214
+ }
215
+ )
216
+ ] })
217
+ ] });
218
+ }
219
+ function ACNIssueTab({ context }) {
220
+ const issueId = context.entityId;
221
+ const companyId = context.companyId;
222
+ const [reviewDone, setReviewDone] = useState(false);
223
+ const { data, loading, error } = usePluginData(
224
+ "acn-task-info",
225
+ issueId && companyId ? { issueId, companyId } : {}
226
+ );
227
+ if (!issueId) {
228
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsx("span", { style: styles.muted, children: "No issue selected." }) });
229
+ }
230
+ if (loading) {
231
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsx("span", { style: styles.muted, children: "Loading\u2026" }) });
232
+ }
233
+ if (error) {
234
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsxs("span", { style: { color: "#dc2626" }, children: [
235
+ "Failed to load ACN data: ",
236
+ error.message
237
+ ] }) });
238
+ }
239
+ if (!data) {
240
+ return /* @__PURE__ */ jsx("div", { style: styles.container, children: /* @__PURE__ */ jsx("span", { style: styles.muted, children: "This issue is not linked to an ACN task." }) });
241
+ }
242
+ const submittedParticipation = data.participations.find(
243
+ (p) => p.status === "submitted"
244
+ );
245
+ const canReview = !reviewDone && Boolean(submittedParticipation);
246
+ return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
247
+ /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
248
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "ACN Task" }),
249
+ /* @__PURE__ */ jsx("span", { style: styles.mono, children: data.task_id })
250
+ ] }),
251
+ /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
252
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Status" }),
253
+ /* @__PURE__ */ jsx(StatusBadge, { status: data.status })
254
+ ] }),
255
+ parseFloat(data.reward ?? "0") > 0 && /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
256
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Reward" }),
257
+ /* @__PURE__ */ jsxs("span", { children: [
258
+ data.reward,
259
+ " ",
260
+ data.reward_currency
261
+ ] })
262
+ ] }),
263
+ data.participations.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.section, children: [
264
+ /* @__PURE__ */ jsxs("div", { style: styles.sectionTitle, children: [
265
+ "Participants (",
266
+ data.participations.length,
267
+ ")"
268
+ ] }),
269
+ data.participations.map((p) => /* @__PURE__ */ jsx(ParticipationItem, { p }, p.participation_id))
270
+ ] }),
271
+ canReview && /* @__PURE__ */ jsx(
272
+ ReviewPanel,
273
+ {
274
+ taskId: data.task_id,
275
+ onDone: () => {
276
+ setReviewDone(true);
277
+ setTimeout(() => setReviewDone(false), 3e3);
278
+ }
279
+ }
280
+ ),
281
+ reviewDone && /* @__PURE__ */ jsx("div", { style: { ...styles.section, color: "#16a34a", fontWeight: 600 }, children: "Review submitted. ACN will process the result." })
282
+ ] });
283
+ }
284
+ export {
285
+ ACNIssueTab
286
+ };
287
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/index.tsx"],
4
+ "sourcesContent": ["import React, { useState } from \"react\";\nimport {\n usePluginAction,\n usePluginData,\n type PluginDetailTabProps,\n} from \"@paperclipai/plugin-sdk/ui\";\n\n// \u2500\u2500 Types shared with worker bridge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface AcnTaskInfo {\n task_id: string;\n title: string;\n status: string;\n /** Decimal string from ACN backend (e.g. \"10.00\"). */\n reward: string;\n reward_currency: string;\n participations: Array<{\n participation_id: string;\n agent_id: string;\n status: string;\n submission_content: string | null;\n submitted_at: string | null;\n resubmit_count: number;\n }>;\n}\n\n// \u2500\u2500 Minimal inline styles (no Tailwind dependency) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst styles = {\n container: {\n padding: \"16px\",\n fontFamily: \"inherit\",\n fontSize: \"13px\",\n color: \"var(--color-fg-default, #1a1a1a)\",\n } as React.CSSProperties,\n\n row: {\n display: \"flex\",\n gap: \"8px\",\n alignItems: \"baseline\",\n marginBottom: \"6px\",\n } as React.CSSProperties,\n\n label: {\n fontWeight: 600,\n color: \"var(--color-fg-muted, #666)\",\n minWidth: \"100px\",\n flexShrink: 0,\n } as React.CSSProperties,\n\n badge: (color: string): React.CSSProperties => ({\n display: \"inline-block\",\n padding: \"2px 8px\",\n borderRadius: \"12px\",\n fontSize: \"11px\",\n fontWeight: 600,\n background: color,\n color: \"#fff\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.04em\",\n }),\n\n section: {\n marginTop: \"16px\",\n borderTop: \"1px solid var(--color-border-default, #e5e5e5)\",\n paddingTop: \"12px\",\n } as React.CSSProperties,\n\n sectionTitle: {\n fontWeight: 700,\n marginBottom: \"8px\",\n fontSize: \"12px\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.06em\",\n color: \"var(--color-fg-muted, #888)\",\n } as React.CSSProperties,\n\n participationCard: {\n background: \"var(--color-bg-subtle, #f6f6f6)\",\n borderRadius: \"6px\",\n padding: \"10px 12px\",\n marginBottom: \"8px\",\n } as React.CSSProperties,\n\n pre: {\n background: \"var(--color-bg-subtle, #f0f0f0)\",\n borderRadius: \"4px\",\n padding: \"8px\",\n fontSize: \"12px\",\n overflowX: \"auto\",\n whiteSpace: \"pre-wrap\",\n wordBreak: \"break-word\",\n marginTop: \"6px\",\n } as React.CSSProperties,\n\n actionRow: {\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"16px\",\n } as React.CSSProperties,\n\n btn: (variant: \"approve\" | \"reject\" | \"neutral\"): React.CSSProperties => ({\n padding: \"6px 16px\",\n borderRadius: \"6px\",\n border: \"none\",\n cursor: \"pointer\",\n fontWeight: 600,\n fontSize: \"13px\",\n background:\n variant === \"approve\"\n ? \"#16a34a\"\n : variant === \"reject\"\n ? \"#dc2626\"\n : \"var(--color-bg-subtle, #e5e5e5)\",\n color: variant === \"neutral\" ? \"var(--color-fg-default, #333)\" : \"#fff\",\n opacity: 1,\n transition: \"opacity 0.15s\",\n }),\n\n textarea: {\n width: \"100%\",\n minHeight: \"64px\",\n borderRadius: \"6px\",\n border: \"1px solid var(--color-border-default, #ccc)\",\n padding: \"6px 8px\",\n fontSize: \"12px\",\n fontFamily: \"inherit\",\n resize: \"vertical\",\n boxSizing: \"border-box\",\n marginTop: \"8px\",\n } as React.CSSProperties,\n\n muted: {\n color: \"var(--color-fg-muted, #888)\",\n fontSize: \"12px\",\n } as React.CSSProperties,\n\n mono: {\n fontFamily: \"monospace\",\n fontSize: \"11px\",\n background: \"var(--color-bg-subtle, #f0f0f0)\",\n padding: \"1px 5px\",\n borderRadius: \"3px\",\n } as React.CSSProperties,\n} as const;\n\n// \u2500\u2500 Status badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst STATUS_COLORS: Record<string, string> = {\n open: \"#2563eb\",\n in_progress: \"#d97706\",\n in_review: \"#7c3aed\",\n completed: \"#16a34a\",\n cancelled: \"#6b7280\",\n rejected: \"#dc2626\",\n submitted: \"#7c3aed\",\n accepted: \"#d97706\",\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n const color = STATUS_COLORS[status] ?? \"#6b7280\";\n return <span style={styles.badge(color)}>{status.replace(/_/g, \" \")}</span>;\n}\n\n// \u2500\u2500 Participation item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ParticipationItem({\n p,\n}: {\n p: AcnTaskInfo[\"participations\"][number];\n}) {\n const [expanded, setExpanded] = useState(false);\n const hasSubmission = Boolean(p.submission_content);\n\n return (\n <div style={styles.participationCard}>\n <div style={styles.row}>\n <span style={styles.label}>Agent</span>\n <span style={styles.mono}>{p.agent_id}</span>\n </div>\n <div style={styles.row}>\n <span style={styles.label}>Status</span>\n <StatusBadge status={p.status} />\n {p.resubmit_count > 0 && (\n <span style={styles.muted}>({p.resubmit_count} resubmit{p.resubmit_count > 1 ? \"s\" : \"\"})</span>\n )}\n </div>\n {p.submitted_at && (\n <div style={styles.row}>\n <span style={styles.label}>Submitted</span>\n <span style={styles.muted}>{new Date(p.submitted_at).toLocaleString()}</span>\n </div>\n )}\n {hasSubmission && (\n <>\n <button\n type=\"button\"\n style={{ ...styles.btn(\"neutral\"), marginTop: \"6px\", fontSize: \"11px\", padding: \"3px 10px\" }}\n onClick={() => setExpanded((v) => !v)}\n >\n {expanded ? \"Hide submission\" : \"Show submission\"}\n </button>\n {expanded && <pre style={styles.pre}>{p.submission_content}</pre>}\n </>\n )}\n </div>\n );\n}\n\n// \u2500\u2500 Review panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ReviewPanel({\n taskId,\n onDone,\n}: {\n taskId: string;\n onDone: () => void;\n}) {\n const [feedback, setFeedback] = useState(\"\");\n const [pending, setPending] = useState<\"approve\" | \"reject\" | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const review = usePluginAction(\"acn-review\");\n\n async function handleReview(approved: boolean) {\n const action = approved ? \"approve\" : \"reject\";\n setPending(action);\n setError(null);\n try {\n await review({ taskId, approved, feedback: feedback.trim() || undefined });\n onDone();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Review failed\");\n } finally {\n setPending(null);\n }\n }\n\n return (\n <div style={styles.section}>\n <div style={styles.sectionTitle}>Review Submission</div>\n <textarea\n style={styles.textarea}\n placeholder=\"Optional feedback\u2026\"\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n disabled={pending !== null}\n />\n {error && <div style={{ color: \"#dc2626\", fontSize: \"12px\", marginTop: \"4px\" }}>{error}</div>}\n <div style={styles.actionRow}>\n <button\n type=\"button\"\n style={{ ...styles.btn(\"approve\"), opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n onClick={() => void handleReview(true)}\n >\n {pending === \"approve\" ? \"Approving\u2026\" : \"Approve\"}\n </button>\n <button\n type=\"button\"\n style={{ ...styles.btn(\"reject\"), opacity: pending ? 0.6 : 1 }}\n disabled={pending !== null}\n onClick={() => void handleReview(false)}\n >\n {pending === \"reject\" ? \"Rejecting\u2026\" : \"Reject\"}\n </button>\n </div>\n </div>\n );\n}\n\n// \u2500\u2500 Main ACN Issue Tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function ACNIssueTab({ context }: PluginDetailTabProps) {\n const issueId = context.entityId;\n const companyId = context.companyId;\n const [reviewDone, setReviewDone] = useState(false);\n\n const { data, loading, error } = usePluginData<AcnTaskInfo | null>(\n \"acn-task-info\",\n issueId && companyId ? { issueId, companyId } : {},\n );\n\n if (!issueId) {\n return <div style={styles.container}><span style={styles.muted}>No issue selected.</span></div>;\n }\n\n if (loading) {\n return <div style={styles.container}><span style={styles.muted}>Loading\u2026</span></div>;\n }\n\n if (error) {\n return (\n <div style={styles.container}>\n <span style={{ color: \"#dc2626\" }}>Failed to load ACN data: {error.message}</span>\n </div>\n );\n }\n\n if (!data) {\n return (\n <div style={styles.container}>\n <span style={styles.muted}>This issue is not linked to an ACN task.</span>\n </div>\n );\n }\n\n const submittedParticipation = data.participations.find(\n (p) => p.status === \"submitted\",\n );\n const canReview = !reviewDone && Boolean(submittedParticipation);\n\n return (\n <div style={styles.container}>\n {/* Task meta */}\n <div style={styles.row}>\n <span style={styles.label}>ACN Task</span>\n <span style={styles.mono}>{data.task_id}</span>\n </div>\n <div style={styles.row}>\n <span style={styles.label}>Status</span>\n <StatusBadge status={data.status} />\n </div>\n {parseFloat(data.reward ?? \"0\") > 0 && (\n <div style={styles.row}>\n <span style={styles.label}>Reward</span>\n <span>{data.reward} {data.reward_currency}</span>\n </div>\n )}\n\n {/* Participations */}\n {data.participations.length > 0 && (\n <div style={styles.section}>\n <div style={styles.sectionTitle}>\n Participants ({data.participations.length})\n </div>\n {data.participations.map((p) => (\n <ParticipationItem key={p.participation_id} p={p} />\n ))}\n </div>\n )}\n\n {/* Review panel \u2014 shown when there is a pending submission */}\n {canReview && (\n <ReviewPanel\n taskId={data.task_id}\n onDone={() => {\n setReviewDone(true);\n setTimeout(() => setReviewDone(false), 3000);\n }}\n />\n )}\n\n {reviewDone && (\n <div style={{ ...styles.section, color: \"#16a34a\", fontWeight: 600 }}>\n Review submitted. ACN will process the result.\n </div>\n )}\n </div>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * ACN Plugin Worker
3
+ *
4
+ * Responsibilities:
5
+ * P0-1 Startup : register webhook URL as ACN subnet harness
6
+ * P0-2 Startup : full sync — pull open ACN tasks → Paperclip issues
7
+ * P0-3 ACN→PC : task.* webhook events → sync Paperclip issue status / comments
8
+ * P0-4 PC→ACN : Paperclip issue done/cancelled → ACN review (approve / reject)
9
+ * P0-5 PC→ACN : Paperclip issue created (not from ACN) → ACN task
10
+ */
11
+ import type { PluginContext, PluginEvent } from "@paperclipai/plugin-sdk";
12
+ import { ACNClient } from "acn-client";
13
+ interface PluginConfig {
14
+ acnBaseUrl?: string;
15
+ paperclipBaseUrl?: string;
16
+ acnApiKeyRef?: string;
17
+ /** Secret ref for the HMAC-SHA256 secret shared with ACN's harness webhook. */
18
+ acnHarnessSecretRef?: string;
19
+ acnSubnetId?: string;
20
+ autoCreateIssues?: boolean;
21
+ autoApproveOnDone?: boolean;
22
+ }
23
+ export declare function handleIssueUpdated(ctx: PluginContext, cfg: PluginConfig, client: ACNClient, event: PluginEvent): Promise<void>;
24
+ export declare function handleIssueCreated(ctx: PluginContext, cfg: PluginConfig, client: ACNClient, event: PluginEvent): Promise<void>;
25
+ declare const plugin: import("@paperclipai/plugin-sdk").PaperclipPlugin;
26
+ export default plugin;
27
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAQlD,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8SD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAwDf;AAID,QAAA,MAAM,MAAM,mDAuJV,CAAC;AAIH,eAAe,MAAM,CAAC"}