paperclip-plugin-google-chat 0.1.2 → 0.1.3

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/config.d.ts CHANGED
@@ -1,13 +1,23 @@
1
1
  /**
2
2
  * Instance configuration for the Google Chat plugin.
3
3
  *
4
- * Secret values (Google Chat incoming-webhook URLs, an optional service-account
5
- * key) are NEVER stored here in plaintext. The config holds **secret references**
6
- * opaque UUIDs minted by Paperclip's secret store — which the worker resolves at
7
- * runtime via `ctx.secrets.resolve(ref)`. This mirrors the Telegram plugin's
8
- * `telegramBotTokenRef` pattern and keeps credentials out of config exports/logs.
4
+ * Webhook URLs can be supplied two ways:
5
+ * - **Secret reference** (`*WebhookUrlRef`, a UUID) resolved at runtime via
6
+ * `ctx.secrets.resolve(ref)`. The most secure option, BUT plugin secret-ref
7
+ * resolution is gated on the host: some Paperclip builds disable it ("Plugin
8
+ * secret references are disabled until company-scoped plugin config lands").
9
+ * - **Raw URL** (`*WebhookUrl`) → the incoming-webhook URL stored directly in
10
+ * config. Works on every build, but the URL (a credential) lives in the
11
+ * instance config in plaintext. The worker **prefers the raw URL** and falls
12
+ * back to the ref, so you can switch to refs once your host supports them.
9
13
  */
10
14
  export interface GoogleChatConfig {
15
+ /** Raw Google Chat *incoming webhook* URL for the default space. */
16
+ defaultWebhookUrl?: string;
17
+ /** Optional per-category raw webhook URLs. */
18
+ approvalsWebhookUrl?: string;
19
+ errorsWebhookUrl?: string;
20
+ digestWebhookUrl?: string;
11
21
  /** Secret ref → a Google Chat *incoming webhook* URL for the default space. */
12
22
  defaultWebhookUrlRef?: string;
13
23
  /** Optional per-category routing. Each is a secret ref to a space webhook URL. */
@@ -29,10 +39,12 @@ export interface GoogleChatConfig {
29
39
  /** Master switch for inbound slash commands. */
30
40
  enableCommands?: boolean;
31
41
  /**
32
- * Shared verification token. Google Chat includes a bearer/verification token on
33
- * each request; we compare it to reject forged webhook deliveries. Stored as a
34
- * secret ref. (For production, prefer verifying the Google-signed bearer JWT.)
42
+ * Shared verification token Google Chat includes on each request; we compare it
43
+ * to reject forged webhook deliveries. Provide EITHER the raw token
44
+ * (`verificationToken`) or a secret ref (`verificationTokenRef`); the worker
45
+ * prefers the raw value. (For production, prefer the Google-signed bearer JWT.)
35
46
  */
47
+ verificationToken?: string;
36
48
  verificationTokenRef?: string;
37
49
  /** Allowlist of Google Chat space IDs permitted to issue commands. Empty = all. */
38
50
  allowedSpaceIds?: string[];
@@ -47,10 +59,27 @@ export declare const DEFAULT_CONFIG: GoogleChatConfig;
47
59
  export declare const INSTANCE_CONFIG_SCHEMA: {
48
60
  readonly type: "object";
49
61
  readonly properties: {
62
+ readonly defaultWebhookUrl: {
63
+ readonly type: "string";
64
+ readonly title: "Default space webhook URL";
65
+ readonly description: "Raw Google Chat incoming-webhook URL for the default space. Use this if your Paperclip build disables plugin secret references; otherwise prefer the secret-ref field below.";
66
+ };
67
+ readonly approvalsWebhookUrl: {
68
+ readonly type: "string";
69
+ readonly title: "Approvals space webhook URL";
70
+ };
71
+ readonly errorsWebhookUrl: {
72
+ readonly type: "string";
73
+ readonly title: "Errors space webhook URL";
74
+ };
75
+ readonly digestWebhookUrl: {
76
+ readonly type: "string";
77
+ readonly title: "Digest space webhook URL";
78
+ };
50
79
  readonly defaultWebhookUrlRef: {
51
80
  readonly type: "string";
52
81
  readonly title: "Default space webhook (secret ref)";
53
- readonly description: "Secret reference to a Google Chat incoming-webhook URL. Create the secret in Paperclip, paste the returned UUID here.";
82
+ readonly description: "Secret reference (UUID) to a Google Chat incoming-webhook URL. Requires host support for plugin secret references; the raw URL field above takes precedence.";
54
83
  };
55
84
  readonly approvalsWebhookUrlRef: {
56
85
  readonly type: "string";
@@ -99,6 +128,10 @@ export declare const INSTANCE_CONFIG_SCHEMA: {
99
128
  readonly title: "Enable inbound slash commands";
100
129
  readonly default: true;
101
130
  };
131
+ readonly verificationToken: {
132
+ readonly type: "string";
133
+ readonly title: "Verification token (raw)";
134
+ };
102
135
  readonly verificationTokenRef: {
103
136
  readonly type: "string";
104
137
  readonly title: "Verification token (secret ref)";
package/dist/config.js CHANGED
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * Instance configuration for the Google Chat plugin.
3
3
  *
4
- * Secret values (Google Chat incoming-webhook URLs, an optional service-account
5
- * key) are NEVER stored here in plaintext. The config holds **secret references**
6
- * opaque UUIDs minted by Paperclip's secret store — which the worker resolves at
7
- * runtime via `ctx.secrets.resolve(ref)`. This mirrors the Telegram plugin's
8
- * `telegramBotTokenRef` pattern and keeps credentials out of config exports/logs.
4
+ * Webhook URLs can be supplied two ways:
5
+ * - **Secret reference** (`*WebhookUrlRef`, a UUID) resolved at runtime via
6
+ * `ctx.secrets.resolve(ref)`. The most secure option, BUT plugin secret-ref
7
+ * resolution is gated on the host: some Paperclip builds disable it ("Plugin
8
+ * secret references are disabled until company-scoped plugin config lands").
9
+ * - **Raw URL** (`*WebhookUrl`) → the incoming-webhook URL stored directly in
10
+ * config. Works on every build, but the URL (a credential) lives in the
11
+ * instance config in plaintext. The worker **prefers the raw URL** and falls
12
+ * back to the ref, so you can switch to refs once your host supports them.
9
13
  */
10
14
  export const DEFAULT_CONFIG = {
11
15
  notifyOnIssueCreated: false,
@@ -23,10 +27,18 @@ export const DEFAULT_CONFIG = {
23
27
  export const INSTANCE_CONFIG_SCHEMA = {
24
28
  type: "object",
25
29
  properties: {
30
+ defaultWebhookUrl: {
31
+ type: "string",
32
+ title: "Default space webhook URL",
33
+ description: "Raw Google Chat incoming-webhook URL for the default space. Use this if your Paperclip build disables plugin secret references; otherwise prefer the secret-ref field below.",
34
+ },
35
+ approvalsWebhookUrl: { type: "string", title: "Approvals space webhook URL" },
36
+ errorsWebhookUrl: { type: "string", title: "Errors space webhook URL" },
37
+ digestWebhookUrl: { type: "string", title: "Digest space webhook URL" },
26
38
  defaultWebhookUrlRef: {
27
39
  type: "string",
28
40
  title: "Default space webhook (secret ref)",
29
- description: "Secret reference to a Google Chat incoming-webhook URL. Create the secret in Paperclip, paste the returned UUID here.",
41
+ description: "Secret reference (UUID) to a Google Chat incoming-webhook URL. Requires host support for plugin secret references; the raw URL field above takes precedence.",
30
42
  },
31
43
  approvalsWebhookUrlRef: { type: "string", title: "Approvals space webhook (secret ref)" },
32
44
  errorsWebhookUrlRef: { type: "string", title: "Errors space webhook (secret ref)" },
@@ -42,6 +54,7 @@ export const INSTANCE_CONFIG_SCHEMA = {
42
54
  notifyOnAgentRunFailed: { type: "boolean", title: "Notify on agent run failed", default: true },
43
55
  useCards: { type: "boolean", title: "Use Cards v2 formatting", default: false },
44
56
  enableCommands: { type: "boolean", title: "Enable inbound slash commands", default: true },
57
+ verificationToken: { type: "string", title: "Verification token (raw)" },
45
58
  verificationTokenRef: { type: "string", title: "Verification token (secret ref)" },
46
59
  allowedSpaceIds: { type: "array", title: "Allowed space IDs", items: { type: "string" } },
47
60
  allowedUserEmails: { type: "array", title: "Allowed user emails", items: { type: "string" } },
@@ -56,15 +69,17 @@ export function validateConfig(config) {
56
69
  if (config.dailyDigestTime && !/^([01]\d|2[0-3]):[0-5]\d$/.test(config.dailyDigestTime)) {
57
70
  errors.push("dailyDigestTime must be 24h HH:MM, e.g. 08:00");
58
71
  }
59
- if (config.digestMode && !config.digestWebhookUrlRef && !config.defaultWebhookUrlRef) {
60
- errors.push("digestMode is on but no digest/default webhook secret ref is set");
72
+ const hasDefault = Boolean(config.defaultWebhookUrl || config.defaultWebhookUrlRef);
73
+ const hasDigest = Boolean(config.digestWebhookUrl || config.digestWebhookUrlRef);
74
+ if (config.digestMode && !hasDigest && !hasDefault) {
75
+ errors.push("digestMode is on but no digest/default webhook (URL or secret ref) is set");
61
76
  }
62
77
  const anyNotify = config.notifyOnIssueCreated ||
63
78
  config.notifyOnIssueCompleted ||
64
79
  config.notifyOnApprovalRequested ||
65
80
  config.notifyOnAgentRunFailed;
66
- if (anyNotify && !config.defaultWebhookUrlRef) {
67
- warnings.push("Notifications are enabled but no defaultWebhookUrlRef is set — category routing only.");
81
+ if (anyNotify && !hasDefault) {
82
+ warnings.push("Notifications are enabled but no default webhook (URL or secret ref) is set — category routing only.");
68
83
  }
69
84
  if (config.serviceAccountKeyRef === "") {
70
85
  warnings.push("serviceAccountKeyRef is an empty string; leave it unset for webhook-only mode.");
@@ -4,7 +4,7 @@
4
4
  * the same convention).
5
5
  */
6
6
  export declare const PLUGIN_ID = "google-chat";
7
- export declare const PLUGIN_VERSION = "0.1.2";
7
+ export declare const PLUGIN_VERSION = "0.1.3";
8
8
  /** Webhook endpoint keys declared in the manifest and matched in `onWebhook`. */
9
9
  export declare const WEBHOOK_KEYS: {
10
10
  /** Google Chat app events (MESSAGE, ADDED_TO_SPACE, CARD_CLICKED, …) POST here. */
package/dist/constants.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * the same convention).
5
5
  */
6
6
  export const PLUGIN_ID = "google-chat";
7
- export const PLUGIN_VERSION = "0.1.2";
7
+ export const PLUGIN_VERSION = "0.1.3";
8
8
  /** Webhook endpoint keys declared in the manifest and matched in `onWebhook`. */
9
9
  export const WEBHOOK_KEYS = {
10
10
  /** Google Chat app events (MESSAGE, ADDED_TO_SPACE, CARD_CLICKED, …) POST here. */
package/dist/worker.js CHANGED
@@ -22,7 +22,20 @@ async function getConfig(ctx) {
22
22
  const raw = (await ctx.config.get());
23
23
  return { ...DEFAULT_CONFIG, ...raw };
24
24
  }
25
- /** Pick the secret ref for a route, falling back to the default webhook. */
25
+ /** Raw webhook URL for a route, falling back to the default. */
26
+ function rawUrlForRoute(config, route) {
27
+ switch (route) {
28
+ case "approvals":
29
+ return config.approvalsWebhookUrl ?? config.defaultWebhookUrl;
30
+ case "errors":
31
+ return config.errorsWebhookUrl ?? config.defaultWebhookUrl;
32
+ case "digest":
33
+ return config.digestWebhookUrl ?? config.defaultWebhookUrl;
34
+ default:
35
+ return config.defaultWebhookUrl;
36
+ }
37
+ }
38
+ /** Secret ref for a route, falling back to the default. */
26
39
  function refForRoute(config, route) {
27
40
  switch (route) {
28
41
  case "approvals":
@@ -35,15 +48,36 @@ function refForRoute(config, route) {
35
48
  return config.defaultWebhookUrlRef;
36
49
  }
37
50
  }
38
- /** Resolve a route's webhook URL (from a secret ref) and POST a message. */
51
+ /**
52
+ * Resolve a route's webhook URL — preferring a raw URL (works on every host),
53
+ * falling back to a secret ref (only where the host enables plugin secret-ref
54
+ * resolution; some builds disable it). Returns null if neither yields a URL.
55
+ */
56
+ async function resolveWebhookUrl(ctx, config, route) {
57
+ const raw = rawUrlForRoute(config, route);
58
+ if (raw)
59
+ return raw;
60
+ const ref = refForRoute(config, route);
61
+ if (!ref)
62
+ return null;
63
+ try {
64
+ return await ctx.secrets.resolve(ref);
65
+ }
66
+ catch (err) {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ ctx.logger?.warn?.(`Could not resolve secret ref for route "${route}" (${msg}). ` +
69
+ `If your Paperclip build disables plugin secret references, set the raw "${route}WebhookUrl" field instead.`);
70
+ return null;
71
+ }
72
+ }
73
+ /** Resolve a route's webhook URL and POST a message. */
39
74
  async function post(ctx, route, message) {
40
75
  const config = await getConfig(ctx);
41
- const ref = refForRoute(config, route);
42
- if (!ref) {
43
- ctx.logger?.warn?.(`No webhook secret ref configured for route "${route}"`);
76
+ const url = await resolveWebhookUrl(ctx, config, route);
77
+ if (!url) {
78
+ ctx.logger?.warn?.(`No webhook URL available for route "${route}"`);
44
79
  return { ok: false, status: 0, body: `no webhook configured for route ${route}` };
45
80
  }
46
- const url = await ctx.secrets.resolve(ref);
47
81
  return postToWebhook((u, init) => ctx.http.fetch(u, init), url, message);
48
82
  }
49
83
  /** Build the inbound command dependencies against the SDK domain APIs. */
@@ -117,10 +151,10 @@ const plugin = definePlugin({
117
151
  async onHealth() {
118
152
  const ctx = currentCtx;
119
153
  const config = ctx ? await getConfig(ctx) : DEFAULT_CONFIG;
120
- const configured = Boolean(config.defaultWebhookUrlRef);
154
+ const configured = Boolean(config.defaultWebhookUrl || config.defaultWebhookUrlRef);
121
155
  return {
122
156
  status: configured ? "ok" : "degraded",
123
- message: configured ? "google-chat plugin ready" : "no defaultWebhookUrlRef configured",
157
+ message: configured ? "google-chat plugin ready" : "no default webhook URL or secret ref configured",
124
158
  details: { commandsEnabled: config.enableCommands !== false, digestMode: config.digestMode === true },
125
159
  };
126
160
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-google-chat",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Bidirectional Google Chat integration for Paperclip — post agent/issue notifications to spaces and drive Paperclip from Chat slash commands.",
5
5
  "type": "module",
6
6
  "license": "MIT",