@aaronsb/google-workspace-mcp 4.3.0 → 4.4.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/README.md +54 -7
- package/build/accounts/oauth.d.ts +16 -0
- package/build/accounts/oauth.js +24 -0
- package/build/accounts/oauth.js.map +1 -1
- package/build/coverage/compare.js +13 -8
- package/build/coverage/compare.js.map +1 -1
- package/build/factory/defaults.js +1 -1
- package/build/factory/defaults.js.map +1 -1
- package/build/factory/generator.d.ts +8 -1
- package/build/factory/generator.js +15 -3
- package/build/factory/generator.js.map +1 -1
- package/build/factory/manifest/contacts.yaml +8 -0
- package/build/factory/manifest/gmail.yaml +15 -0
- package/build/factory/safety.d.ts +61 -5
- package/build/factory/safety.js +109 -4
- package/build/factory/safety.js.map +1 -1
- package/build/factory/types.d.ts +16 -0
- package/build/server/batch.d.ts +20 -0
- package/build/server/batch.js +292 -0
- package/build/server/batch.js.map +1 -0
- package/build/server/handler.d.ts +5 -0
- package/build/server/handler.js +21 -9
- package/build/server/handler.js.map +1 -1
- package/build/server/queue.js +11 -1
- package/build/server/queue.js.map +1 -1
- package/build/server/scratchpad/handler.js +65 -0
- package/build/server/scratchpad/handler.js.map +1 -1
- package/build/server/server.js +15 -5
- package/build/server/server.js.map +1 -1
- package/build/server/tools.js +54 -6
- package/build/server/tools.js.map +1 -1
- package/build/services/contacts/patch.d.ts +26 -0
- package/build/services/contacts/patch.js +7 -1
- package/build/services/contacts/patch.js.map +1 -1
- package/build/services/docs/patch.js +1 -1
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/package.json +1 -1
package/build/factory/safety.js
CHANGED
|
@@ -11,13 +11,28 @@
|
|
|
11
11
|
* - Log/audit destructive actions
|
|
12
12
|
*
|
|
13
13
|
* Policies are composable — multiple can apply to the same operation.
|
|
14
|
-
* They're configured per-deployment, not per-service.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
15
|
+
* Most are configured per-deployment via GWS_SAFETY_POLICY:
|
|
17
16
|
* - "Draft-only mode" — agents can read email but not send
|
|
18
17
|
* - "No-delete mode" — prevent permanent deletion across all services
|
|
19
18
|
* - "Audit mode" — log all write operations to stderr
|
|
19
|
+
*
|
|
20
|
+
* `account-access` is the exception and is always active. It enforces a per-ACCOUNT
|
|
21
|
+
* choice the user made at consent time rather than a deployment-wide one, and it blocks
|
|
22
|
+
* by returning a reason rather than throwing. See ADR-202.
|
|
23
|
+
*
|
|
24
|
+
* WHO RUNS THIS: `generateHandler` calls it for every factory-generated operation, and
|
|
25
|
+
* `manage_scratchpad` calls it explicitly for its send and sync writes (#171) because it
|
|
26
|
+
* is hand-registered and gets no policy check for free. Any future hand-registered tool
|
|
27
|
+
* that writes to Google has to do the same — nothing enforces that from here, which is
|
|
28
|
+
* exactly how scratchpad went uncovered.
|
|
29
|
+
*
|
|
30
|
+
* `manage_accounts` and `manage_workspace` are also hand-registered and need no check:
|
|
31
|
+
* neither writes through a Google API.
|
|
20
32
|
*/
|
|
33
|
+
import { readCredential } from '../accounts/credentials.js';
|
|
34
|
+
import { anyScopesFor, writeScopesFor } from '../accounts/oauth.js';
|
|
35
|
+
import { loadDescriptor } from '../google/descriptor.js';
|
|
21
36
|
// ── Built-in policies ────────────────────────────────────────────────
|
|
22
37
|
/**
|
|
23
38
|
* Draft-only email policy — blocks send/reply/forward, allows everything else.
|
|
@@ -107,6 +122,96 @@ export const readOnly = {
|
|
|
107
122
|
return { action: 'allow' };
|
|
108
123
|
},
|
|
109
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Access policy — refuse a write from an account that was authorized read-only, and say
|
|
127
|
+
* so in a sentence, before the request leaves. ADR-202.
|
|
128
|
+
*
|
|
129
|
+
* This is the enforcement half of per-account access. The consent half narrows the token;
|
|
130
|
+
* this half explains what happened when the narrowed token is asked to do more. Without
|
|
131
|
+
* it Google still refuses — with a 403 that names neither the account, nor the scope, nor
|
|
132
|
+
* the way out.
|
|
133
|
+
*
|
|
134
|
+
* UNLIKE EVERY OTHER POLICY HERE, this one is always on. The others express a deployment
|
|
135
|
+
* choice an operator opts into with GWS_SAFETY_POLICY. This one enforces a choice the
|
|
136
|
+
* *user* already made at consent time, so requiring a second opt-in to honour it would
|
|
137
|
+
* make the first one decorative.
|
|
138
|
+
*
|
|
139
|
+
* It fails OPEN in every case where it cannot be sure: an account with no credential, an
|
|
140
|
+
* unreadable credential file, a service with no scope map, an unknown method. A safety
|
|
141
|
+
* check that blocks on its own uncertainty stops being a safety check and becomes an
|
|
142
|
+
* outage — and the operations at issue are ones Google will independently refuse anyway.
|
|
143
|
+
*/
|
|
144
|
+
export const accountAccess = {
|
|
145
|
+
name: 'account-access',
|
|
146
|
+
description: 'Refuse writes from a read-only account, naming the account and the fix',
|
|
147
|
+
applies: () => true,
|
|
148
|
+
evaluate: async (_args, ctx, _service, op) => {
|
|
149
|
+
// No account (a service that needs none) or no manifest info: nothing to check against.
|
|
150
|
+
if (!ctx.account || !op)
|
|
151
|
+
return { action: 'allow' };
|
|
152
|
+
let granted;
|
|
153
|
+
try {
|
|
154
|
+
const cred = await readCredential(ctx.account);
|
|
155
|
+
granted = new Set(cred.scopes ?? []);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
// Not authenticated, or the credential is unreadable. The auth path already has a
|
|
159
|
+
// good error for that; inventing a second one here would only obscure it.
|
|
160
|
+
//
|
|
161
|
+
// Say so on stderr regardless. Blocks are logged; a silent allow is not, so a
|
|
162
|
+
// truncated credential file would otherwise disable enforcement for that account
|
|
163
|
+
// with no signal anywhere — the failure mode you least want to be quiet.
|
|
164
|
+
process.stderr.write(`[google-workspace-mcp] safety: account-access NOT enforcing for ${ctx.account} — ` +
|
|
165
|
+
`could not read its credential (${err instanceof Error ? err.message : String(err)})\n`);
|
|
166
|
+
return { action: 'allow' };
|
|
167
|
+
}
|
|
168
|
+
if (granted.size === 0)
|
|
169
|
+
return { action: 'allow' };
|
|
170
|
+
const required = await requiredScopes(op);
|
|
171
|
+
if (required.length === 0)
|
|
172
|
+
return { action: 'allow' };
|
|
173
|
+
if (required.some((scope) => granted.has(scope)))
|
|
174
|
+
return { action: 'allow' };
|
|
175
|
+
// Nothing the operation accepts was granted. Two different causes, two different fixes.
|
|
176
|
+
const holdsSomething = anyScopesFor(op.service).some((scope) => granted.has(scope));
|
|
177
|
+
const reconsent = `manage_accounts {operation:'scopes', email:'${ctx.account}', ` +
|
|
178
|
+
`services:'${op.service}', access:'readwrite'}`;
|
|
179
|
+
return {
|
|
180
|
+
action: 'block',
|
|
181
|
+
reason: holdsSomething
|
|
182
|
+
? `'${ctx.operation}' needs write access to ${op.service}. ` +
|
|
183
|
+
`Account ${ctx.account} was authorized read-only for ${op.service}. ` +
|
|
184
|
+
`Re-authorize with ${reconsent}, or use an account that already has it.`
|
|
185
|
+
: `'${ctx.operation}' needs access to ${op.service}, which account ${ctx.account} ` +
|
|
186
|
+
`has never authorized. Grant it with ${reconsent}, or use an account that already has it.`,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* The scopes Google will accept for this operation.
|
|
192
|
+
*
|
|
193
|
+
* Prefers the descriptor, which is Google's own per-method scope list (ADR-103) and so
|
|
194
|
+
* cannot drift from what Google actually enforces. A hand-kept table of write operations
|
|
195
|
+
* would be a second source of truth — the #161 failure.
|
|
196
|
+
*
|
|
197
|
+
* Seven of 95 operations declare no `resource` because they are pure custom handlers that
|
|
198
|
+
* make several calls (`gmail.send`, `drive.upload`, `calendar.create` and friends), and
|
|
199
|
+
* six of those seven are writes. Leaving them unenforced would exempt exactly the
|
|
200
|
+
* operations most worth enforcing, so they fall back to the manifest's declared `type`,
|
|
201
|
+
* which a test already pins against the descriptor's httpMethod.
|
|
202
|
+
*/
|
|
203
|
+
async function requiredScopes(op) {
|
|
204
|
+
if (op.resource) {
|
|
205
|
+
const descriptor = await loadDescriptor();
|
|
206
|
+
const method = descriptor.services[op.googleService]?.methods?.[op.resource];
|
|
207
|
+
// An unknown method means the manifest and the descriptor disagree. That is a bug
|
|
208
|
+
// worth failing on, but not here, at the cost of the user's request.
|
|
209
|
+
if (method?.scopes?.length)
|
|
210
|
+
return method.scopes;
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
return op.type === 'action' ? writeScopesFor(op.service) : anyScopesFor(op.service);
|
|
214
|
+
}
|
|
110
215
|
/**
|
|
111
216
|
* Audit policy — allows everything but logs destructive operations to stderr.
|
|
112
217
|
* Useful for monitoring what an agent does without blocking it.
|
|
@@ -142,11 +247,11 @@ export function getActivePolicies() {
|
|
|
142
247
|
* Run all active policies against an operation.
|
|
143
248
|
* First block wins. Returns the most restrictive result.
|
|
144
249
|
*/
|
|
145
|
-
export function evaluatePolicies(args, ctx, service) {
|
|
250
|
+
export async function evaluatePolicies(args, ctx, service, op) {
|
|
146
251
|
for (const policy of activePolicies) {
|
|
147
252
|
if (!policy.applies(service, ctx.operation))
|
|
148
253
|
continue;
|
|
149
|
-
const result = policy.evaluate(args, ctx, service);
|
|
254
|
+
const result = await policy.evaluate(args, ctx, service, op);
|
|
150
255
|
if (result.action === 'block') {
|
|
151
256
|
process.stderr.write(`[google-workspace-mcp] safety: BLOCKED ${service}.${ctx.operation} by ${policy.name}: ${result.reason}\n`);
|
|
152
257
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/factory/safety.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/factory/safety.ts"],"names":[],"mappings":"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"}
|
package/build/factory/types.d.ts
CHANGED
|
@@ -36,6 +36,22 @@ export interface OperationDef {
|
|
|
36
36
|
description: string;
|
|
37
37
|
/** Google resource path (e.g. "users.messages.list"). */
|
|
38
38
|
resource?: string;
|
|
39
|
+
/**
|
|
40
|
+
* How this operation is done for many resources in ONE request. ADR-308.
|
|
41
|
+
*
|
|
42
|
+
* Present only where Google publishes such a method — six operations across two
|
|
43
|
+
* services. Its absence is what makes an operation unbatchable, so the batchable set is
|
|
44
|
+
* derived from the manifest and never listed anywhere else.
|
|
45
|
+
*
|
|
46
|
+
* `resource` here is not always the singular method pluralised: Gmail has no bulk
|
|
47
|
+
* trash, so `trash` batches through `users.messages.batchModify` with `defaults`
|
|
48
|
+
* supplying the TRASH label. `defaults` is merged into the request body and never
|
|
49
|
+
* enters the generated schema, exactly as the operation-level `defaults` does.
|
|
50
|
+
*/
|
|
51
|
+
batch?: {
|
|
52
|
+
resource: string;
|
|
53
|
+
defaults?: Record<string, unknown>;
|
|
54
|
+
};
|
|
39
55
|
/** Named parameters the caller can provide. */
|
|
40
56
|
params?: Record<string, ParamDef>;
|
|
41
57
|
/** Default values merged into the params sent to Google. */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HandlerResponse } from './formatting/markdown.js';
|
|
2
|
+
/** One batchable operation, as the manifest declares it. */
|
|
3
|
+
export interface BatchableOp {
|
|
4
|
+
tool: string;
|
|
5
|
+
operation: string;
|
|
6
|
+
service: string;
|
|
7
|
+
googleService: string;
|
|
8
|
+
resource: string;
|
|
9
|
+
defaults: Record<string, unknown>;
|
|
10
|
+
type: 'list' | 'detail' | 'action';
|
|
11
|
+
}
|
|
12
|
+
/** Every operation the manifest says can batch, keyed `tool.operation`. */
|
|
13
|
+
export declare function batchableOperations(): Map<string, BatchableOp>;
|
|
14
|
+
/**
|
|
15
|
+
* Run one batch call.
|
|
16
|
+
*
|
|
17
|
+
* Refusing is as important as running: an operation with no `batch:` block cannot be
|
|
18
|
+
* batched, and saying so with the list of what can turns a dead end into the answer.
|
|
19
|
+
*/
|
|
20
|
+
export declare function handleBatch(params: Record<string, unknown>): Promise<HandlerResponse>;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch mode for `bulk_operations` — one Google request across many resources. ADR-308.
|
|
3
|
+
*
|
|
4
|
+
* The rule that makes one shape serve five different Google methods: **top-level
|
|
5
|
+
* arguments are shared across the batch, `items` carry what differs**. A batch is one
|
|
6
|
+
* call with one set of parameters applied to many resources, so a parameter that varies
|
|
7
|
+
* per item is not a batch — it is a queue.
|
|
8
|
+
*
|
|
9
|
+
* Which operations can batch is DERIVED from the manifest's `batch:` blocks. Nothing here
|
|
10
|
+
* lists them. This repository has produced the hand-maintained-list-beside-a-generated-one
|
|
11
|
+
* defect three times (the hardcoded queue tool enum, the stale coverage baseline, #161),
|
|
12
|
+
* and a list of batchable operations would be the fourth.
|
|
13
|
+
*/
|
|
14
|
+
import { loadManifest } from '../factory/generator.js';
|
|
15
|
+
import { evaluatePolicies } from '../factory/safety.js';
|
|
16
|
+
import { call } from '../google/client.js';
|
|
17
|
+
import { buildPerson } from '../services/contacts/patch.js';
|
|
18
|
+
/** Every operation the manifest says can batch, keyed `tool.operation`. */
|
|
19
|
+
export function batchableOperations() {
|
|
20
|
+
const out = new Map();
|
|
21
|
+
for (const [service, def] of Object.entries(loadManifest().services)) {
|
|
22
|
+
for (const [operation, op] of Object.entries(def.operations)) {
|
|
23
|
+
if (!op.batch)
|
|
24
|
+
continue;
|
|
25
|
+
out.set(`${def.tool_name}.${operation}`, {
|
|
26
|
+
tool: def.tool_name,
|
|
27
|
+
operation,
|
|
28
|
+
service,
|
|
29
|
+
googleService: def.google_service,
|
|
30
|
+
resource: op.batch.resource,
|
|
31
|
+
defaults: op.batch.defaults ?? {},
|
|
32
|
+
type: op.type,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** `manage_email modify, manage_contacts create, …` — for telling a caller what does batch. */
|
|
39
|
+
function batchableList() {
|
|
40
|
+
return [...batchableOperations().values()]
|
|
41
|
+
.map((b) => `${b.tool} ${b.operation}`)
|
|
42
|
+
.join(', ');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The one field an item is mostly likely to be, per method — so a caller with a list of
|
|
46
|
+
* ids can pass a list of ids.
|
|
47
|
+
*
|
|
48
|
+
* `items: ['people/c1', 'people/c2']` says everything `items: [{contactId:'people/c1'},
|
|
49
|
+
* {contactId:'people/c2'}]` says, with less to get wrong. The object form still works for
|
|
50
|
+
* the operations that carry more than an id.
|
|
51
|
+
*/
|
|
52
|
+
const ITEM_ID_FIELD = {
|
|
53
|
+
'users.messages.batchModify': 'messageId',
|
|
54
|
+
'people.batchDeleteContacts': 'contactId',
|
|
55
|
+
'people.getBatchGet': 'contactId',
|
|
56
|
+
'people.batchUpdateContacts': 'contactId',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Accept a bare id where an object is expected, and a bare contact id where Google wants
|
|
60
|
+
* the `people/` prefix — the same courtesy the single-contact operations extend.
|
|
61
|
+
*/
|
|
62
|
+
function normalizeItems(items, resource) {
|
|
63
|
+
const field = ITEM_ID_FIELD[resource];
|
|
64
|
+
return items.map((item) => {
|
|
65
|
+
const obj = (typeof item === 'string' && field)
|
|
66
|
+
? { [field]: item }
|
|
67
|
+
: item;
|
|
68
|
+
const id = obj?.contactId ?? obj?.resourceName;
|
|
69
|
+
if (typeof id === 'string' && id && !id.startsWith('people/')) {
|
|
70
|
+
return { ...obj, contactId: `people/${id}` };
|
|
71
|
+
}
|
|
72
|
+
return obj;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const BODY_BUILDERS = {
|
|
76
|
+
// ids + the label changes, which are shared by definition: one call sets one label set.
|
|
77
|
+
'users.messages.batchModify': (shared, items) => ({
|
|
78
|
+
ids: items.map((i) => str(i.messageId)),
|
|
79
|
+
...(shared.addLabelIds ? { addLabelIds: list(shared.addLabelIds) } : {}),
|
|
80
|
+
...(shared.removeLabelIds ? { removeLabelIds: list(shared.removeLabelIds) } : {}),
|
|
81
|
+
}),
|
|
82
|
+
// Each contact is different, so the whole payload comes from items. `buildPerson` is
|
|
83
|
+
// the same converter single `create` uses, so a batch item takes the same flat fields —
|
|
84
|
+
// name, contactEmail, phone. Passing them raw is a 400 from Google, measured; the raw
|
|
85
|
+
// People shape is still accepted via `person` for anything the flat fields cannot say.
|
|
86
|
+
'people.batchCreateContacts': (shared, items) => ({
|
|
87
|
+
contacts: items.map((i) => ({ contactPerson: i.person ?? buildPerson(i).person })),
|
|
88
|
+
readMask: shared.readMask ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
|
|
89
|
+
}),
|
|
90
|
+
'people.batchDeleteContacts': (_shared, items) => ({
|
|
91
|
+
resourceNames: items.map((i) => str(i.contactId ?? i.resourceName)),
|
|
92
|
+
}),
|
|
93
|
+
// A map keyed by resource name rather than an array — Google's shape, not ours. Each
|
|
94
|
+
// person must carry its current etag, which `prepare` below fetches; without it Google
|
|
95
|
+
// answers FAILED_PRECONDITION (measured).
|
|
96
|
+
'people.batchUpdateContacts': (shared, items) => {
|
|
97
|
+
const touched = new Set();
|
|
98
|
+
const contacts = {};
|
|
99
|
+
for (const i of items) {
|
|
100
|
+
const built = i.person ? { person: i.person, touched: new Set() } : buildPerson(i);
|
|
101
|
+
for (const f of built.touched)
|
|
102
|
+
touched.add(f);
|
|
103
|
+
contacts[str(i.contactId ?? i.resourceName)] = { ...built.person, etag: i.etag };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
contacts,
|
|
107
|
+
// Derived from the fields the caller actually supplied, exactly as single `update`
|
|
108
|
+
// does. A fixed mask would name fields nobody touched, and naming a field with no
|
|
109
|
+
// value in the body CLEARS it.
|
|
110
|
+
updateMask: shared.updateMask ?? [...touched].join(','),
|
|
111
|
+
readMask: shared.readMask ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
// A GET: resourceNames is a repeated QUERY parameter, not a body.
|
|
115
|
+
'people.getBatchGet': (shared, items) => ({
|
|
116
|
+
resourceNames: items.map((i) => str(i.contactId ?? i.resourceName)),
|
|
117
|
+
personFields: shared.personFields ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
function str(v) {
|
|
121
|
+
return typeof v === 'string' ? v : String(v ?? '');
|
|
122
|
+
}
|
|
123
|
+
/** Accept `'A,B'` or `['A','B']` — Google always wants the array. */
|
|
124
|
+
function list(v) {
|
|
125
|
+
if (Array.isArray(v))
|
|
126
|
+
return v.map(str);
|
|
127
|
+
return str(v).split(',').map((s) => s.trim()).filter(Boolean);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Work some methods need before the batch call itself.
|
|
131
|
+
*
|
|
132
|
+
* `batchUpdateContacts` requires each person's current etag — Google answers
|
|
133
|
+
* FAILED_PRECONDITION without one (measured). Fetching them is one `getBatchGet`, so a
|
|
134
|
+
* batch update costs two calls for N contacts rather than the 2N a queue would.
|
|
135
|
+
*/
|
|
136
|
+
const PREPARE = {
|
|
137
|
+
'people.batchUpdateContacts': async (items, account) => {
|
|
138
|
+
const names = items.map((i) => str(i.contactId ?? i.resourceName));
|
|
139
|
+
const got = await call('people', 'people.getBatchGet', {
|
|
140
|
+
resourceNames: names,
|
|
141
|
+
personFields: 'names,emailAddresses,phoneNumbers,organizations,biographies,metadata',
|
|
142
|
+
}, { account });
|
|
143
|
+
const etags = new Map();
|
|
144
|
+
for (const r of got.responses ?? []) {
|
|
145
|
+
const person = r.person;
|
|
146
|
+
if (person?.resourceName)
|
|
147
|
+
etags.set(str(person.resourceName), person.etag);
|
|
148
|
+
}
|
|
149
|
+
return items.map((i) => {
|
|
150
|
+
const name = str(i.contactId ?? i.resourceName);
|
|
151
|
+
const etag = etags.get(name);
|
|
152
|
+
if (etag === undefined)
|
|
153
|
+
throw new Error(`No contact found for ${name} — cannot update what does not exist.`);
|
|
154
|
+
return { ...i, contactId: name, etag };
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
/** A person as one line, matching how the single-contact operations print. */
|
|
159
|
+
function personLine(person) {
|
|
160
|
+
const names = person.names ?? [];
|
|
161
|
+
const display = names[0]?.displayName ?? [names[0]?.givenName, names[0]?.familyName].filter(Boolean).join(' ');
|
|
162
|
+
const emails = person.emailAddresses ?? [];
|
|
163
|
+
const parts = [str(person.resourceName), str(display || '(no name)')];
|
|
164
|
+
if (emails[0]?.value)
|
|
165
|
+
parts.push(str(emails[0].value));
|
|
166
|
+
return parts.join(' | ');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Render what came back, so a batch READ is worth making.
|
|
170
|
+
*
|
|
171
|
+
* `refs` never reaches the model — the server returns `result.text` only — so anything
|
|
172
|
+
* the agent must act on has to be in the text. Live, `get` answered "2 results returned"
|
|
173
|
+
* and threw the two people away.
|
|
174
|
+
*/
|
|
175
|
+
function renderPeople(data) {
|
|
176
|
+
const rows = [];
|
|
177
|
+
for (const r of data.responses ?? []) {
|
|
178
|
+
if (r.person)
|
|
179
|
+
rows.push(personLine(r.person));
|
|
180
|
+
}
|
|
181
|
+
for (const r of data.createdPeople ?? []) {
|
|
182
|
+
if (r.person)
|
|
183
|
+
rows.push(personLine(r.person));
|
|
184
|
+
}
|
|
185
|
+
const updated = data.updateResult;
|
|
186
|
+
for (const r of Object.values(updated ?? {})) {
|
|
187
|
+
if (r.person)
|
|
188
|
+
rows.push(personLine(r.person));
|
|
189
|
+
}
|
|
190
|
+
return rows.length ? rows.join('\n') : null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Run one batch call.
|
|
194
|
+
*
|
|
195
|
+
* Refusing is as important as running: an operation with no `batch:` block cannot be
|
|
196
|
+
* batched, and saying so with the list of what can turns a dead end into the answer.
|
|
197
|
+
*/
|
|
198
|
+
export async function handleBatch(params) {
|
|
199
|
+
const tool = params.tool;
|
|
200
|
+
const operation = params.operation;
|
|
201
|
+
const rawItems = params.items;
|
|
202
|
+
if (!tool || !operation) {
|
|
203
|
+
return err(`mode:'batch' needs \`tool\` and \`operation\`. Operations that batch: ${batchableList()}.`);
|
|
204
|
+
}
|
|
205
|
+
if (!Array.isArray(rawItems) || rawItems.length === 0) {
|
|
206
|
+
return err(`mode:'batch' needs a non-empty \`items\` array — one entry per resource to act on.`);
|
|
207
|
+
}
|
|
208
|
+
const batchable = batchableOperations();
|
|
209
|
+
const spec = batchable.get(`${tool}.${operation}`);
|
|
210
|
+
if (!spec) {
|
|
211
|
+
// Deliberately does not claim "Google publishes no batch method". For docs and
|
|
212
|
+
// sheets that is false — they publish `batchUpdate`, which batches many edits to ONE
|
|
213
|
+
// document rather than one edit across many. Saying otherwise would teach the caller
|
|
214
|
+
// something untrue about Google's API.
|
|
215
|
+
return err(`${tool} '${operation}' cannot be batched. ` +
|
|
216
|
+
`Operations that can: ${batchableList()}. ` +
|
|
217
|
+
`Use mode:'queue' instead, which works for every operation.`);
|
|
218
|
+
}
|
|
219
|
+
const items = normalizeItems(rawItems, spec.resource);
|
|
220
|
+
const build = BODY_BUILDERS[spec.resource];
|
|
221
|
+
if (!build) {
|
|
222
|
+
// A manifest declared a batch resource this file cannot build a body for. That is a
|
|
223
|
+
// bug in this repo, not in the caller's request, so it says so plainly.
|
|
224
|
+
return err(`${tool} '${operation}' declares batch resource ${spec.resource}, which has no body builder.`);
|
|
225
|
+
}
|
|
226
|
+
const account = params.email;
|
|
227
|
+
if (!account)
|
|
228
|
+
return err(`mode:'batch' needs \`email\` — the account to act as.`);
|
|
229
|
+
// Batch reaches Google without passing through generateHandler, so it gets no policy
|
|
230
|
+
// check for free — the same way manage_scratchpad did not (#171). Doing many writes at
|
|
231
|
+
// once is the last place to skip this: a read-only account would otherwise be refused
|
|
232
|
+
// one contact deletion and permitted two hundred.
|
|
233
|
+
const decision = await evaluatePolicies([], { operation: spec.operation, params, account }, spec.googleService, {
|
|
234
|
+
service: spec.service,
|
|
235
|
+
googleService: spec.googleService,
|
|
236
|
+
resource: spec.resource,
|
|
237
|
+
type: spec.type,
|
|
238
|
+
});
|
|
239
|
+
if (decision.action === 'block') {
|
|
240
|
+
return {
|
|
241
|
+
text: `**Blocked by safety policy:** ${decision.reason}`,
|
|
242
|
+
refs: { blocked: true, error: true, policy: decision.reason },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Shared arguments are everything the caller passed that is not batch plumbing.
|
|
246
|
+
const { mode: _m, tool: _t, operation: _o, items: _i, email: _e, detail: _d, ...shared } = params;
|
|
247
|
+
let prepared = items;
|
|
248
|
+
const prepare = PREPARE[spec.resource];
|
|
249
|
+
if (prepare) {
|
|
250
|
+
try {
|
|
251
|
+
prepared = await prepare(items, account);
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const body = { ...spec.defaults, ...build({ ...spec.defaults, ...shared }, prepared) };
|
|
258
|
+
const data = await call(spec.googleService, spec.resource, body, { account });
|
|
259
|
+
const rendered = renderPeople((data ?? {}));
|
|
260
|
+
return {
|
|
261
|
+
text: `## Batch: ${tool} ${operation}\n\n` +
|
|
262
|
+
`${items.length} item${items.length === 1 ? '' : 's'} in one request ` +
|
|
263
|
+
`(\`${spec.resource}\`).\n\n${rendered ?? summarize(data)}`,
|
|
264
|
+
refs: { batch: true, tool, operation, resource: spec.resource, count: items.length, data },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Say what Google said, without inventing per-item detail it did not send.
|
|
269
|
+
*
|
|
270
|
+
* The methods differ here and the difference is not ours to paper over: batchModify and
|
|
271
|
+
* batchDelete answer 204 with no body, so a partial failure is not distinguishable per
|
|
272
|
+
* item; batchCreateContacts answers with a result per contact. Presenting one uniform
|
|
273
|
+
* shape would mean fabricating the missing half.
|
|
274
|
+
*/
|
|
275
|
+
function summarize(data) {
|
|
276
|
+
if (data === undefined || data === null || (typeof data === 'object' && Object.keys(data).length === 0)) {
|
|
277
|
+
return 'Google accepted the request and returned no body, which is how it reports success for this method. It does not report per-item status.';
|
|
278
|
+
}
|
|
279
|
+
const rec = data;
|
|
280
|
+
for (const key of ['createContacts', 'updateResult', 'responses']) {
|
|
281
|
+
const arr = rec[key];
|
|
282
|
+
if (Array.isArray(arr))
|
|
283
|
+
return `${arr.length} result${arr.length === 1 ? '' : 's'} returned.`;
|
|
284
|
+
}
|
|
285
|
+
if (rec.responses || rec.createContacts || rec.updateResult)
|
|
286
|
+
return 'Completed.';
|
|
287
|
+
return 'Completed.';
|
|
288
|
+
}
|
|
289
|
+
function err(text) {
|
|
290
|
+
return { text, refs: { error: true } };
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=batch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/server/batch.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,4 +4,9 @@ import type { HandlerResponse } from './formatting/markdown.js';
|
|
|
4
4
|
export declare function getEpoch(): number;
|
|
5
5
|
/** Increment and return the new epoch. Called once per tool dispatch. */
|
|
6
6
|
export declare function advanceEpoch(): number;
|
|
7
|
+
/**
|
|
8
|
+
* The names that reach the bulk handler. `queue_operations` is the former name, kept
|
|
9
|
+
* working for one minor release — ADR-308. Both are advertised and both dispatch here.
|
|
10
|
+
*/
|
|
11
|
+
export declare const BULK_TOOL_NAMES: string[];
|
|
7
12
|
export declare function handleToolCall(toolName: string, params: Record<string, unknown>): Promise<HandlerResponse>;
|
package/build/server/handler.js
CHANGED
|
@@ -2,6 +2,7 @@ import { handleAccounts } from './handlers/accounts.js';
|
|
|
2
2
|
import { handleWorkspace } from './handlers/workspace.js';
|
|
3
3
|
import { handleScratchpad } from './scratchpad/handler.js';
|
|
4
4
|
import { handleQueue } from './queue.js';
|
|
5
|
+
import { handleBatch } from './batch.js';
|
|
5
6
|
import { generatedTools } from '../factory/registry.js';
|
|
6
7
|
import { getSessionTracker, sessionContext } from './session/index.js';
|
|
7
8
|
// ── Epoch counter ─────────────────────────────────────────
|
|
@@ -27,25 +28,36 @@ for (const tool of generatedTools) {
|
|
|
27
28
|
domainHandlers[tool.schema.name] = tool.handler;
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* The names that reach the bulk handler. `queue_operations` is the former name, kept
|
|
32
|
+
* working for one minor release — ADR-308. Both are advertised and both dispatch here.
|
|
33
|
+
*/
|
|
34
|
+
export const BULK_TOOL_NAMES = ['bulk_operations', 'queue_operations'];
|
|
35
|
+
/**
|
|
36
|
+
* What a queued operation may call: every tool, including `bulk_operations` itself.
|
|
37
|
+
*
|
|
38
|
+
* A bulk call is a tool call like any other, so the set of things it can name is the set
|
|
39
|
+
* of tools — no carve-out, and nothing for a caller to discover by being refused. Nesting
|
|
40
|
+
* is bounded by depth inside handleQueue rather than by omission here, so the limit
|
|
41
|
+
* arrives as a sentence saying what it is instead of an "Unknown tool" for something the
|
|
42
|
+
* server plainly advertises.
|
|
31
43
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* bounded by depth inside handleQueue rather than by omission here, so the limit arrives
|
|
35
|
-
* as a sentence saying what it is instead of an "Unknown tool" for something the server
|
|
36
|
-
* plainly advertises.
|
|
44
|
+
* The old name is queueable too. An agent that learned it before the rename should not
|
|
45
|
+
* find it working at the top level and failing one level down.
|
|
37
46
|
*/
|
|
38
47
|
function queueableHandlers(depth) {
|
|
48
|
+
const nested = (params) => handleQueue(params, queueableHandlers(depth + 1), depth + 1);
|
|
39
49
|
return {
|
|
40
50
|
...domainHandlers,
|
|
41
|
-
|
|
51
|
+
...Object.fromEntries(BULK_TOOL_NAMES.map((name) => [name, nested])),
|
|
42
52
|
};
|
|
43
53
|
}
|
|
44
54
|
export async function handleToolCall(toolName, params) {
|
|
45
55
|
const currentEpoch = advanceEpoch();
|
|
46
56
|
const tracker = getSessionTracker();
|
|
47
|
-
//
|
|
48
|
-
if (toolName
|
|
57
|
+
// Bulk wraps the domain handlers (each queued op also advances the epoch)
|
|
58
|
+
if (BULK_TOOL_NAMES.includes(toolName)) {
|
|
59
|
+
if (params.mode === 'batch')
|
|
60
|
+
return await handleBatch(params);
|
|
49
61
|
const result = await handleQueue(params, queueableHandlers(1), 1);
|
|
50
62
|
const queueEmail = extractEmailFromQueue(params);
|
|
51
63
|
if (queueEmail) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOvE,6DAA6D;AAC7D,gEAAgE;AAChE,mEAAmE;AAEnE,IAAI,KAAK,GAAG,CAAC,CAAC;AAEd,2BAA2B;AAC3B,MAAM,UAAU,QAAQ;IACtB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY;IAC1B,OAAO,EAAE,KAAK,CAAC;AACjB,CAAC;AAED,6DAA6D;AAE7D,MAAM,cAAc,GAAgC;IAClD,eAAe,EAAE,cAAc;IAC/B,gBAAgB,EAAE,eAAe;IACjC,iBAAiB,EAAE,gBAAgB;CACpC,CAAC;AAEF,sCAAsC;AACtC,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;IAClC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;AAClD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"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"}
|
package/build/server/queue.js
CHANGED
|
@@ -23,7 +23,7 @@ function stripNextSteps(text) {
|
|
|
23
23
|
const MAX_QUEUE_DEPTH = 3;
|
|
24
24
|
export async function handleQueue(params, handlers, depth = 1) {
|
|
25
25
|
if (depth > MAX_QUEUE_DEPTH) {
|
|
26
|
-
throw new Error(`
|
|
26
|
+
throw new Error(`bulk_operations nested more than ${MAX_QUEUE_DEPTH} deep. Each level multiplies the ` +
|
|
27
27
|
`work — at 10 operations per queue that ceiling is already 1,000 calls. Flatten the ` +
|
|
28
28
|
`inner queues into their parent.`);
|
|
29
29
|
}
|
|
@@ -65,6 +65,16 @@ export async function handleQueue(params, handlers, depth = 1) {
|
|
|
65
65
|
advanceEpoch(); // Each queued operation is a logical tool call
|
|
66
66
|
const response = await handler(resolvedArgs);
|
|
67
67
|
const text = stripNextSteps(response.text);
|
|
68
|
+
// A safety policy declines by RETURNING, not by throwing, so the catch below never
|
|
69
|
+
// sees it. Counting that as a success made a queue report "2/2 succeeded" having
|
|
70
|
+
// written nothing, and left `onError: 'bail'` running the rest of a queue whose
|
|
71
|
+
// first step was refused.
|
|
72
|
+
if (response.refs?.blocked) {
|
|
73
|
+
results.push({ index: i, tool: op.tool, status: 'error', error: text });
|
|
74
|
+
if (errorStrategy === 'bail')
|
|
75
|
+
bailedAt = i;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
68
78
|
results.push({ index: i, tool: op.tool, status: 'success', text, refs: response.refs });
|
|
69
79
|
lastSuccessText = response.text; // keep next-steps from last success
|
|
70
80
|
}
|