@alfe.ai/openclaw-google 0.0.41 → 0.0.42

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,543 @@
1
+ let node_child_process = require("node:child_process");
2
+ let node_module = require("node:module");
3
+ let node_os = require("node:os");
4
+ let _sinclair_typebox = require("@sinclair/typebox");
5
+ let _alfe_ai_config = require("@alfe.ai/config");
6
+ let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
7
+ let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
8
+ let node_fs = require("node:fs");
9
+ let node_path = require("node:path");
10
+ //#region src/boundary.ts
11
+ const MAX_ACCOUNTS = 128;
12
+ const MAX_EMAIL_CHARS = 320;
13
+ const MAX_DISPLAY_NAME_CHARS = 512;
14
+ const MAX_COMMAND_CHARS = 64 * 1024;
15
+ const MAX_COMMAND_ARGS = 256;
16
+ const MAX_FILE_PATH_CHARS = 8192;
17
+ const MAX_TOOL_OUTPUT_BYTES = 256 * 1024;
18
+ const EMAIL_PATTERN$1 = /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/u;
19
+ const DESTRUCTIVE_METHODS = new Set([
20
+ "batchclear",
21
+ "batchdelete",
22
+ "clear",
23
+ "delete",
24
+ "emptytrash",
25
+ "purge",
26
+ "remove",
27
+ "trash",
28
+ "wipe"
29
+ ]);
30
+ const FILE_FLAGS = new Set(["--output", "--upload"]);
31
+ const CHILD_ENV_ALLOWLIST = [
32
+ "HOME",
33
+ "HTTP_PROXY",
34
+ "HTTPS_PROXY",
35
+ "LANG",
36
+ "LC_ALL",
37
+ "LC_CTYPE",
38
+ "NO_PROXY",
39
+ "PATH",
40
+ "SSL_CERT_DIR",
41
+ "SSL_CERT_FILE",
42
+ "TZ"
43
+ ];
44
+ function normalizeGoogleAccounts(value, homeDirectory = (0, node_os.homedir)()) {
45
+ const root = requireRecord(value, "Google accounts response");
46
+ if (!Array.isArray(root.accounts) || root.accounts.length > MAX_ACCOUNTS) throw new Error(`Google accounts response must contain at most ${String(MAX_ACCOUNTS)} accounts`);
47
+ const selectors = /* @__PURE__ */ new Set();
48
+ const configDirectories = /* @__PURE__ */ new Set();
49
+ return root.accounts.map((raw, index) => {
50
+ const account = requireRecord(raw, `Google account ${String(index)}`);
51
+ const email = validateGoogleEmail(account.email, "Google account email");
52
+ const selector = email.toLowerCase();
53
+ if (selectors.has(selector)) throw new Error("Google accounts response contains a duplicate email");
54
+ selectors.add(selector);
55
+ const configDir = resolveConfigDir(email, homeDirectory);
56
+ const configKey = configDir.toLowerCase();
57
+ if (configDirectories.has(configKey)) throw new Error("Google account emails map to an ambiguous gws config directory");
58
+ configDirectories.add(configKey);
59
+ return {
60
+ email,
61
+ displayName: optionalDisplayText(account.displayName, "Google account display name"),
62
+ connectedAt: optionalConnectedAt(account.connectedAt),
63
+ configDir
64
+ };
65
+ });
66
+ }
67
+ function normalizeAccountSelector(value) {
68
+ return validateGoogleEmail(value, "Google account selector").toLowerCase();
69
+ }
70
+ function sanitizeEmail(email) {
71
+ return validateGoogleEmail(email, "Google account email").replace(/@/gu, "-").replace(/\./gu, "-");
72
+ }
73
+ function resolveConfigDir(email, homeDirectory = (0, node_os.homedir)()) {
74
+ return (0, node_path.join)(homeDirectory, ".config", `gws-${sanitizeEmail(email)}`);
75
+ }
76
+ function parseGwsCommand(value, confirmation) {
77
+ const command = requireBoundedString(value, "command", MAX_COMMAND_CHARS).trim();
78
+ if (!command) throw new Error("Command cannot be empty");
79
+ if (hasControlCharacter(command)) throw new Error("Command cannot contain control characters");
80
+ const args = tokenizeCommand(command);
81
+ if (args.length === 0) throw new Error("Command cannot be empty");
82
+ if (args.length > MAX_COMMAND_ARGS) throw new Error(`Command cannot contain more than ${String(MAX_COMMAND_ARGS)} arguments`);
83
+ if (args[0]?.toLowerCase() === "auth") throw new Error("Authentication commands are managed by the Google integration");
84
+ const positional = args.slice(0, args.findIndex((arg) => arg.startsWith("--")) === -1 ? args.length : args.findIndex((arg) => arg.startsWith("--")));
85
+ const destructive = !args.includes("--dry-run") && positional.some((arg) => DESTRUCTIVE_METHODS.has(arg.toLowerCase()));
86
+ if (destructive && confirmation !== command) throw new Error("confirmCommand must exactly match command for a destructive gws operation");
87
+ return {
88
+ command,
89
+ args,
90
+ destructive
91
+ };
92
+ }
93
+ function confineGwsFileArguments(args, workspacePath) {
94
+ let workspaceRealPath;
95
+ try {
96
+ workspaceRealPath = (0, node_fs.realpathSync)(workspacePath);
97
+ } catch {
98
+ throw new Error("Configured workspace is unavailable");
99
+ }
100
+ if (!(0, node_fs.statSync)(workspaceRealPath).isDirectory()) throw new Error("Configured workspace path is not a directory");
101
+ const rewritten = [...args];
102
+ for (let index = 0; index < rewritten.length; index += 1) {
103
+ const argument = rewritten.at(index);
104
+ if (argument === void 0) continue;
105
+ const equalIndex = argument.indexOf("=");
106
+ const flag = equalIndex === -1 ? argument : argument.slice(0, equalIndex);
107
+ if (!FILE_FLAGS.has(flag)) continue;
108
+ const inlineValue = equalIndex === -1 ? void 0 : argument.slice(equalIndex + 1);
109
+ const valueIndex = inlineValue === void 0 ? index + 1 : index;
110
+ const rawPath = inlineValue ?? rewritten.at(valueIndex);
111
+ if (rawPath === void 0 || rawPath.length === 0 || rawPath.startsWith("--")) throw new Error(`${flag} requires a workspace-relative file path`);
112
+ const confined = flag === "--upload" ? confineUploadPath(rawPath, workspaceRealPath) : confineOutputPath(rawPath, workspaceRealPath);
113
+ if (inlineValue === void 0) {
114
+ rewritten[valueIndex] = confined;
115
+ index = valueIndex;
116
+ } else rewritten[index] = `${flag}=${confined}`;
117
+ }
118
+ return {
119
+ args: rewritten,
120
+ workspacePath: workspaceRealPath
121
+ };
122
+ }
123
+ function buildGwsEnvironment(configDir, source = process.env) {
124
+ const environment = Object.create(null);
125
+ for (const name of CHILD_ENV_ALLOWLIST) {
126
+ const value = source[name];
127
+ if (value !== void 0 && !hasControlCharacter(value)) environment[name] = value;
128
+ }
129
+ environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR = requireBoundedString(configDir, "gws config directory", MAX_FILE_PATH_CHARS);
130
+ if (hasControlCharacter(environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR)) throw new Error("gws config directory cannot contain control characters");
131
+ return environment;
132
+ }
133
+ function boundToolOutput(value) {
134
+ const buffer = Buffer.from(typeof value === "string" ? value : "", "utf8");
135
+ if (buffer.byteLength <= 262144) return {
136
+ text: buffer.toString("utf8"),
137
+ truncated: false
138
+ };
139
+ return {
140
+ text: `${buffer.subarray(0, MAX_TOOL_OUTPUT_BYTES).toString("utf8")}\n[output truncated]`,
141
+ truncated: true
142
+ };
143
+ }
144
+ function safeGwsDiagnostic(value, secrets = []) {
145
+ let redacted = value.slice(0, 4096).replace(/\b(Bearer|Basic)\s+\S+/giu, "$1 [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password)\s*[=:]\s*\S+/giu, "$1=[REDACTED]").replace(/\balfe_[A-Za-z0-9_-]{8,}/gu, "[REDACTED]");
146
+ for (const secret of secrets) if (secret.length >= 4) redacted = redacted.split(secret).join("[REDACTED]");
147
+ let flattened = "";
148
+ for (const character of redacted) {
149
+ const codePoint = character.codePointAt(0) ?? 0;
150
+ flattened += codePoint < 32 || codePoint === 127 ? " " : character;
151
+ }
152
+ return flattened.trim();
153
+ }
154
+ function tokenizeCommand(command) {
155
+ const args = [];
156
+ let current = "";
157
+ let tokenStarted = false;
158
+ let quote = null;
159
+ let escaped = false;
160
+ for (const character of command) {
161
+ if (escaped) {
162
+ current += character;
163
+ tokenStarted = true;
164
+ escaped = false;
165
+ continue;
166
+ }
167
+ if (quote === "single") {
168
+ if (character === "'") quote = null;
169
+ else current += character;
170
+ tokenStarted = true;
171
+ continue;
172
+ }
173
+ if (quote === "double") {
174
+ if (character === "\"") quote = null;
175
+ else if (character === "\\") escaped = true;
176
+ else current += character;
177
+ tokenStarted = true;
178
+ continue;
179
+ }
180
+ if (character === "'") {
181
+ quote = "single";
182
+ tokenStarted = true;
183
+ } else if (character === "\"") {
184
+ quote = "double";
185
+ tokenStarted = true;
186
+ } else if (character === "\\") {
187
+ escaped = true;
188
+ tokenStarted = true;
189
+ } else if (/\s/u.test(character)) {
190
+ if (tokenStarted) {
191
+ args.push(current);
192
+ current = "";
193
+ tokenStarted = false;
194
+ }
195
+ } else {
196
+ current += character;
197
+ tokenStarted = true;
198
+ }
199
+ }
200
+ if (quote !== null || escaped) throw new Error("Command contains an unterminated quote or escape");
201
+ if (tokenStarted) args.push(current);
202
+ return args;
203
+ }
204
+ function confineUploadPath(value, workspaceRealPath) {
205
+ const candidate = resolveCandidatePath(value, workspaceRealPath);
206
+ try {
207
+ if ((0, node_fs.lstatSync)(candidate).isSymbolicLink()) throw new Error("unsafe upload");
208
+ const realCandidate = (0, node_fs.realpathSync)(candidate);
209
+ if (!isWithin(workspaceRealPath, realCandidate) || !(0, node_fs.statSync)(realCandidate).isFile()) throw new Error("unsafe upload");
210
+ return realCandidate;
211
+ } catch {
212
+ throw new Error("Upload path must be a regular file inside the configured workspace");
213
+ }
214
+ }
215
+ function confineOutputPath(value, workspaceRealPath) {
216
+ const candidate = resolveCandidatePath(value, workspaceRealPath);
217
+ let parent;
218
+ try {
219
+ parent = (0, node_fs.realpathSync)((0, node_path.dirname)(candidate));
220
+ } catch {
221
+ throw new Error("Output path must have an existing directory inside the configured workspace");
222
+ }
223
+ if (!isWithin(workspaceRealPath, parent)) throw new Error("Output path must stay inside the configured workspace");
224
+ try {
225
+ const status = (0, node_fs.lstatSync)(candidate);
226
+ if (status.isSymbolicLink() || status.isDirectory()) throw new Error("Output path must be a non-symlink file inside the configured workspace");
227
+ if (!isWithin(workspaceRealPath, (0, node_fs.realpathSync)(candidate))) throw new Error("Output path must stay inside the configured workspace");
228
+ } catch (error) {
229
+ if ((isRecord(error) ? error.code : void 0) !== "ENOENT") throw new Error("Output path must be a non-symlink file inside the configured workspace");
230
+ }
231
+ return candidate;
232
+ }
233
+ function resolveCandidatePath(value, workspaceRealPath) {
234
+ const rawPath = requireBoundedString(value, "gws file path", MAX_FILE_PATH_CHARS);
235
+ if (hasControlCharacter(rawPath)) throw new Error("gws file path cannot contain control characters");
236
+ return (0, node_path.isAbsolute)(rawPath) ? (0, node_path.resolve)(rawPath) : (0, node_path.resolve)(workspaceRealPath, rawPath);
237
+ }
238
+ function isWithin(root, candidate) {
239
+ const child = (0, node_path.relative)(root, candidate);
240
+ return child === "" || !(0, node_path.isAbsolute)(child) && child !== ".." && !child.startsWith(`..${node_path.sep}`);
241
+ }
242
+ function validateGoogleEmail(value, label) {
243
+ const email = requireBoundedString(value, label, MAX_EMAIL_CHARS);
244
+ if (email !== email.trim() || email.includes("/") || email.includes("\\") || !EMAIL_PATTERN$1.test(email)) throw new Error(`${label} is invalid`);
245
+ return email;
246
+ }
247
+ function optionalDisplayText(value, label) {
248
+ if (value === void 0 || value === null) return void 0;
249
+ const text = requireBoundedString(value, label, MAX_DISPLAY_NAME_CHARS);
250
+ if (hasControlCharacter(text)) throw new Error(`${label} contains control characters`);
251
+ return text;
252
+ }
253
+ function optionalConnectedAt(value) {
254
+ if (value === void 0 || value === null) return void 0;
255
+ const connectedAt = requireBoundedString(value, "Google account connectedAt", 128);
256
+ if (Number.isNaN(Date.parse(connectedAt))) throw new Error("Google account connectedAt is invalid");
257
+ return connectedAt;
258
+ }
259
+ function requireBoundedString(value, label, maxLength) {
260
+ if (typeof value !== "string" || value.length < 1 || value.length > maxLength) throw new Error(`${label} must be a non-empty string of at most ${String(maxLength)} characters`);
261
+ return value;
262
+ }
263
+ function requireRecord(value, label) {
264
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
265
+ return value;
266
+ }
267
+ function isRecord(value) {
268
+ return value !== null && typeof value === "object" && !Array.isArray(value);
269
+ }
270
+ function hasControlCharacter(value) {
271
+ for (const character of value) {
272
+ const codePoint = character.codePointAt(0) ?? 0;
273
+ if (codePoint < 32 || codePoint === 127) return true;
274
+ }
275
+ return false;
276
+ }
277
+ const PLUGIN_VERSION = validatePackageVersion((0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json").version);
278
+ const GOOGLE_ACTIVATION_KEY = (0, _alfe_ai_openclaw_plugin_kit.getActivationKey)("google");
279
+ const RUNTIME_STATE_KEY = "__alfeGooglePluginRuntimeState";
280
+ const GWS_TIMEOUT_MS = 6e4;
281
+ const GWS_MAX_BUFFER_BYTES = 1024 * 1024;
282
+ const EMAIL_PATTERN = "^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$";
283
+ function createGooglePluginRuntimeState() {
284
+ return {
285
+ client: null,
286
+ workspacePath: null,
287
+ accounts: [],
288
+ refreshPromise: null,
289
+ generation: 0
290
+ };
291
+ }
292
+ function createGooglePlugin(dependencies = {}) {
293
+ const resolveRuntimeConfig = dependencies.resolveConfig ?? _alfe_ai_config.resolveConfig;
294
+ const createClient = dependencies.createClient ?? ((config) => new _alfe_ai_agent_api_client.AgentApiClient(config));
295
+ const executeGws = dependencies.executeGws ?? executeGwsCommand;
296
+ const installErrorCapture = dependencies.installErrorCapture ?? _alfe_ai_agent_api_client.installToolErrorCapture;
297
+ const homeDirectory = dependencies.homeDirectory ?? (0, node_os.homedir)();
298
+ const getState = () => dependencies.runtimeState ?? getGlobalRuntimeState();
299
+ const ensureClient = () => {
300
+ const state = getState();
301
+ if (state.client === null || state.workspacePath === null) {
302
+ const config = resolveRuntimeConfig();
303
+ state.client = createClient({
304
+ apiKey: config.apiKey,
305
+ apiUrl: config.apiUrl
306
+ });
307
+ state.workspacePath = config.workspacePath;
308
+ }
309
+ return {
310
+ client: state.client,
311
+ workspacePath: state.workspacePath
312
+ };
313
+ };
314
+ const refreshAccounts = async () => {
315
+ const state = getState();
316
+ if (state.refreshPromise !== null) return state.refreshPromise;
317
+ const { client } = ensureClient();
318
+ const generation = state.generation;
319
+ const refresh = client.getGoogleCredentials().then((value) => {
320
+ const accounts = normalizeGoogleAccounts(value, homeDirectory);
321
+ if (state.generation === generation && state.client === client) state.accounts = accounts;
322
+ return accounts;
323
+ });
324
+ state.refreshPromise = refresh;
325
+ try {
326
+ return await refresh;
327
+ } finally {
328
+ if (state.refreshPromise === refresh) state.refreshPromise = null;
329
+ }
330
+ };
331
+ const resolveAccount = async (value) => {
332
+ const selector = normalizeAccountSelector(value);
333
+ let account = getState().accounts.find((candidate) => candidate.email.toLowerCase() === selector);
334
+ if (account === void 0) account = (await refreshAccounts()).find((candidate) => candidate.email.toLowerCase() === selector);
335
+ if (account === void 0) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("Google account not found; call google_list_accounts first");
336
+ return account;
337
+ };
338
+ const tools = [
339
+ (0, _alfe_ai_openclaw_plugin_kit.defineTool)({
340
+ name: "google_list_accounts",
341
+ description: "List connected Google Workspace accounts. Use this before a credential-touching tool when the exact account email is not already known.",
342
+ parameters: _sinclair_typebox.Type.Object({}, { additionalProperties: false }),
343
+ handler: async () => {
344
+ const accounts = await refreshAccounts();
345
+ return {
346
+ accounts: accounts.map(({ email, displayName, connectedAt }) => ({
347
+ email,
348
+ displayName,
349
+ connectedAt
350
+ })),
351
+ count: accounts.length
352
+ };
353
+ }
354
+ }),
355
+ (0, _alfe_ai_openclaw_plugin_kit.defineTool)({
356
+ name: "google_run_command",
357
+ description: "Run a Google Workspace CLI command for one explicitly selected account. The command supports quoted JSON exactly like gws (for example: drive files list --params '{\"pageSize\": 10}'). Authentication commands are blocked. --upload and --output paths must remain inside the configured workspace. Use --dry-run to preview mutations; otherwise copy an exact destructive command into confirmCommand when requested.",
358
+ parameters: _sinclair_typebox.Type.Object({
359
+ command: _sinclair_typebox.Type.String({
360
+ description: "gws arguments without the leading gws executable",
361
+ minLength: 1,
362
+ maxLength: MAX_COMMAND_CHARS
363
+ }),
364
+ email: _sinclair_typebox.Type.String({
365
+ description: "Exact email returned by google_list_accounts",
366
+ minLength: 3,
367
+ maxLength: 320,
368
+ pattern: EMAIL_PATTERN
369
+ }),
370
+ confirmCommand: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({
371
+ description: "Exact command, required for destructive methods unless --dry-run is present",
372
+ minLength: 1,
373
+ maxLength: MAX_COMMAND_CHARS
374
+ }))
375
+ }, { additionalProperties: false }),
376
+ handler: async (params) => {
377
+ let parsed;
378
+ try {
379
+ parsed = parseGwsCommand(params.command, params.confirmCommand);
380
+ } catch (error) {
381
+ throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(error instanceof Error ? error.message : "Invalid Google Workspace command");
382
+ }
383
+ const account = await resolveAccount(params.email);
384
+ const { workspacePath } = ensureClient();
385
+ const result = await executeGws({
386
+ args: parsed.args,
387
+ configDir: account.configDir,
388
+ workspacePath
389
+ });
390
+ if (result.exitCode !== 0) {
391
+ const diagnostic = safeGwsDiagnostic(result.stderr || result.stdout, [account.configDir, workspacePath]);
392
+ return {
393
+ status: "error",
394
+ error: `gws command failed with exit code ${String(result.exitCode)}`,
395
+ ...diagnostic ? { diagnostic } : {},
396
+ truncated: result.truncated
397
+ };
398
+ }
399
+ return {
400
+ account: account.email,
401
+ command: `gws ${parsed.command}`,
402
+ ...result
403
+ };
404
+ }
405
+ }),
406
+ (0, _alfe_ai_openclaw_plugin_kit.defineTool)({
407
+ name: "google_disconnect_account",
408
+ description: "Permanently disconnect one Google account from this agent. Copy the exact account email into confirmEmail only after the user approves.",
409
+ parameters: _sinclair_typebox.Type.Object({
410
+ email: _sinclair_typebox.Type.String({
411
+ description: "Exact email returned by google_list_accounts",
412
+ minLength: 3,
413
+ maxLength: 320,
414
+ pattern: EMAIL_PATTERN
415
+ }),
416
+ confirmEmail: _sinclair_typebox.Type.String({
417
+ description: "Exact selected account email confirming permanent disconnect",
418
+ minLength: 3,
419
+ maxLength: 320,
420
+ pattern: EMAIL_PATTERN
421
+ })
422
+ }, { additionalProperties: false }),
423
+ handler: async (params) => {
424
+ const account = await resolveAccount(params.email);
425
+ if (params.confirmEmail !== account.email) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("confirmEmail must exactly match the selected Google account email");
426
+ const { client } = ensureClient();
427
+ const result = await client.disconnectGoogleAccount(account.email);
428
+ let remaining;
429
+ let refreshRequired = false;
430
+ try {
431
+ remaining = normalizeGoogleAccounts(result, homeDirectory);
432
+ } catch {
433
+ remaining = [];
434
+ refreshRequired = true;
435
+ }
436
+ getState().accounts = remaining;
437
+ return {
438
+ message: `${account.email} has been disconnected`,
439
+ remainingAccounts: remaining.map(({ email, displayName, connectedAt }) => ({
440
+ email,
441
+ displayName,
442
+ connectedAt
443
+ })),
444
+ ...refreshRequired ? { refreshRequired: true } : {}
445
+ };
446
+ }
447
+ })
448
+ ];
449
+ const stop = (log, message) => {
450
+ const state = getState();
451
+ state.generation += 1;
452
+ state.client = null;
453
+ state.workspacePath = null;
454
+ state.accounts = [];
455
+ state.refreshPromise = null;
456
+ (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(GOOGLE_ACTIVATION_KEY);
457
+ log.info(message);
458
+ };
459
+ return {
460
+ id: "@alfe.ai/openclaw-google",
461
+ name: "Alfe Google Workspace Plugin",
462
+ description: "Multi-account Google Workspace management with an explicit account selector",
463
+ version: PLUGIN_VERSION,
464
+ activate(api) {
465
+ installErrorCapture(api, { plugin: "openclaw-google" });
466
+ for (const tool of tools) api.registerTool(tool);
467
+ api.logger.info(`Registered ${String(tools.length)} Google tools: ${tools.map((tool) => tool.name).join(", ")}`);
468
+ api.registerService?.({
469
+ id: "alfe-google-workspace",
470
+ start: () => {
471
+ (0, _alfe_ai_openclaw_plugin_kit.guardedStart)(GOOGLE_ACTIVATION_KEY, api.logger, async () => {
472
+ const state = getState();
473
+ state.generation += 1;
474
+ ensureClient();
475
+ try {
476
+ const accounts = await refreshAccounts();
477
+ api.logger.info(`Cached ${String(accounts.length)} Google account(s)`);
478
+ } catch {
479
+ api.logger.warn("Google account pre-cache failed; tools will retry on demand");
480
+ }
481
+ api.logger.info("Alfe Google Workspace plugin activated");
482
+ });
483
+ },
484
+ stop: () => {
485
+ stop(api.logger, "Alfe Google Workspace plugin stopped");
486
+ }
487
+ });
488
+ },
489
+ deactivate(api) {
490
+ stop(api.logger, "Alfe Google Workspace plugin deactivated");
491
+ }
492
+ };
493
+ }
494
+ async function executeGwsCommand(options) {
495
+ const confined = confineGwsFileArguments(options.args, options.workspacePath);
496
+ const environment = buildGwsEnvironment(options.configDir);
497
+ return new Promise((resolveResult) => {
498
+ (0, node_child_process.execFile)("gws", confined.args, {
499
+ cwd: confined.workspacePath,
500
+ env: environment,
501
+ timeout: GWS_TIMEOUT_MS,
502
+ maxBuffer: GWS_MAX_BUFFER_BYTES,
503
+ windowsHide: true
504
+ }, (error, stdout, stderr) => {
505
+ const boundedStdout = boundToolOutput(stdout);
506
+ const boundedStderr = boundToolOutput(stderr || (error && typeof error.code === "string" ? "gws command could not be started" : ""));
507
+ resolveResult({
508
+ stdout: boundedStdout.text,
509
+ stderr: boundedStderr.text,
510
+ exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
511
+ truncated: boundedStdout.truncated || boundedStderr.truncated
512
+ });
513
+ });
514
+ });
515
+ }
516
+ function getGlobalRuntimeState() {
517
+ const globalRecord = globalThis;
518
+ const existing = globalRecord[RUNTIME_STATE_KEY];
519
+ if (isRuntimeState(existing)) return existing;
520
+ const state = createGooglePluginRuntimeState();
521
+ globalRecord[RUNTIME_STATE_KEY] = state;
522
+ return state;
523
+ }
524
+ function isRuntimeState(value) {
525
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
526
+ const candidate = value;
527
+ return (candidate.client === null || typeof candidate.client === "object") && (candidate.workspacePath === null || typeof candidate.workspacePath === "string") && Array.isArray(candidate.accounts) && (candidate.refreshPromise === null || candidate.refreshPromise instanceof Promise) && typeof candidate.generation === "number";
528
+ }
529
+ function validatePackageVersion(value) {
530
+ if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) throw new Error("openclaw-google package version is invalid");
531
+ return value;
532
+ }
533
+ //#endregion
534
+ //#region src/plugin.ts
535
+ /** OpenClaw extension entry: default-only to preserve plugin loader interop. */
536
+ const plugin = createGooglePlugin();
537
+ //#endregion
538
+ Object.defineProperty(exports, "plugin", {
539
+ enumerable: true,
540
+ get: function() {
541
+ return plugin;
542
+ }
543
+ });