@sema-agent/client-core 0.82.1 → 0.82.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/CHANGELOG.md +16 -0
- package/README.md +6 -5
- package/dist/adapter/activeRunSelfHeal.d.ts +4 -0
- package/dist/adapter/activeRunSelfHeal.js +46 -14
- package/dist/cloudConfigWireCaps.d.ts +77 -2
- package/dist/cloudConfigWireCaps.js +405 -8
- package/dist/hitl/frameRouter.d.ts +2 -1
- package/dist/hitl/hitlBridge.d.ts +29 -1
- package/dist/hitl/hitlBridge.js +135 -3
- package/dist/hitl/parkResolver.js +6 -2
- package/dist/hitl/planReviewWire.d.ts +6 -0
- package/dist/hitl/planReviewWire.js +1 -1
- package/dist/hitl/toolApprovalWire.d.ts +2 -1
- package/dist/hitl/toolApprovalWire.js +104 -97
- package/dist/notifications.d.ts +3 -1
- package/dist/notifications.js +18 -2
- package/docs/INTEGRATION-CLIENTS.md +78 -6
- package/package.json +1 -1
|
@@ -114,19 +114,153 @@ export function cloudSkillsToSpecs(manifestSkills, resolveBody) {
|
|
|
114
114
|
}
|
|
115
115
|
return specs;
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
const REGISTRY_ENTRY_ANNOTATION_KEYS = ['origin', 'overridesTeam'];
|
|
118
|
+
function withoutRegistryAnnotations(e) {
|
|
119
|
+
const r = asRec(e);
|
|
120
|
+
if (!r || !REGISTRY_ENTRY_ANNOTATION_KEYS.some((k) => Object.hasOwn(r, k)))
|
|
121
|
+
return e;
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [k, v] of Object.entries(r)) {
|
|
124
|
+
if (REGISTRY_ENTRY_ANNOTATION_KEYS.includes(k))
|
|
125
|
+
continue;
|
|
126
|
+
setOwn(out, k, v);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
const TIER_WORDS = Object.freeze(['ultra', 'max', 'pro', 'flash', 'lite']);
|
|
131
|
+
const TIER_GROUP_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
132
|
+
const setOwn = (o, k, v) => {
|
|
133
|
+
Object.defineProperty(o, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
134
|
+
};
|
|
135
|
+
function modelsDocOf(models, opts) {
|
|
118
136
|
const m = asRec(models);
|
|
119
137
|
if (!m || !Array.isArray(m.models))
|
|
120
|
-
return null;
|
|
138
|
+
return { doc: null, issues: [] };
|
|
121
139
|
const entries = m.models.filter((e) => {
|
|
122
140
|
const r = asRec(e);
|
|
123
141
|
return !!r && !!asStr(r.name) && !!asStr(r.id);
|
|
124
142
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
const stripped = entries.map(withoutRegistryAnnotations);
|
|
144
|
+
const allNames = new Set(entries.map((e) => e.name));
|
|
145
|
+
const rawNames = new Set(m.models.map((e) => asStr(asRec(e)?.name)).filter((n) => !!n));
|
|
146
|
+
const accept = typeof opts?.entryAccepted === 'function' ? opts.entryAccepted : undefined;
|
|
147
|
+
const acceptedName = (e) => {
|
|
148
|
+
try {
|
|
149
|
+
const copy = JSON.parse(JSON.stringify(e));
|
|
150
|
+
return typeof copy === 'object' && copy !== null && accept(copy) === true;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
129
155
|
};
|
|
156
|
+
const acceptedFlags = accept ? stripped.map(acceptedName) : undefined;
|
|
157
|
+
const names = acceptedFlags ? new Set(stripped.filter((_, i) => acceptedFlags[i]).map((e) => e.name)) : allNames;
|
|
158
|
+
const issues = [];
|
|
159
|
+
const drop = (i) => {
|
|
160
|
+
issues.push({ ...i, outcome: 'dropped' });
|
|
161
|
+
};
|
|
162
|
+
const refOf = (v) => (typeof v === 'string' && v !== '' ? { target: v } : {});
|
|
163
|
+
const reasonOf = (v) => typeof v === 'string' && v !== '' ? (rawNames.has(v) ? 'rejected' : 'dangling') : 'unreadable';
|
|
164
|
+
let written = stripped;
|
|
165
|
+
if (acceptedFlags && stripped.length > 0) {
|
|
166
|
+
const anyAccepted = acceptedFlags.some((f) => f);
|
|
167
|
+
stripped.forEach((e, i) => {
|
|
168
|
+
if (!acceptedFlags[i])
|
|
169
|
+
issues.push({ key: 'models', target: e.name, reason: 'rejected', outcome: anyAccepted ? 'dropped' : 'kept' });
|
|
170
|
+
});
|
|
171
|
+
if (anyAccepted)
|
|
172
|
+
written = stripped.filter((_, i) => acceptedFlags[i]);
|
|
173
|
+
}
|
|
174
|
+
const doc = { models: written };
|
|
175
|
+
if (accept && Object.hasOwn(m, 'default')) {
|
|
176
|
+
const d = m.default;
|
|
177
|
+
if (typeof d === 'string' && names.has(d))
|
|
178
|
+
doc.default = d;
|
|
179
|
+
else
|
|
180
|
+
drop({ key: 'default', ...refOf(d), reason: reasonOf(d) });
|
|
181
|
+
}
|
|
182
|
+
const roles = asRec(m.roles);
|
|
183
|
+
if (roles) {
|
|
184
|
+
const kept = {};
|
|
185
|
+
for (const [role, t] of Object.entries(roles)) {
|
|
186
|
+
const tr = asRec(t);
|
|
187
|
+
const model = tr && Object.hasOwn(tr, 'model') ? tr.model : undefined;
|
|
188
|
+
if (typeof model === 'string' && !names.has(model)) {
|
|
189
|
+
drop({ key: 'roles', at: role, ...refOf(model), reason: reasonOf(model) });
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
setOwn(kept, role, t);
|
|
193
|
+
}
|
|
194
|
+
doc.roles = kept;
|
|
195
|
+
}
|
|
196
|
+
if (Array.isArray(m.atModelAllowlist)) {
|
|
197
|
+
const src = m.atModelAllowlist;
|
|
198
|
+
const keep = src.filter((n) => typeof n === 'string' && names.has(n));
|
|
199
|
+
if (keep.length === 0 && src.length > 0) {
|
|
200
|
+
doc.atModelAllowlist = src;
|
|
201
|
+
for (const n of src)
|
|
202
|
+
issues.push({ key: 'atModelAllowlist', ...refOf(n), reason: reasonOf(n), outcome: 'kept' });
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
for (const n of src)
|
|
206
|
+
if (!(typeof n === 'string' && names.has(n)))
|
|
207
|
+
drop({ key: 'atModelAllowlist', ...refOf(n), reason: reasonOf(n) });
|
|
208
|
+
doc.atModelAllowlist = keep;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
let groupNames;
|
|
212
|
+
const rawGroupNames = new Set((Array.isArray(m.tierGroups) ? m.tierGroups : []).map((g) => asStr(asRec(g)?.name)).filter((n) => !!n));
|
|
213
|
+
if (accept && Object.hasOwn(m, 'tierGroups')) {
|
|
214
|
+
if (!Array.isArray(m.tierGroups)) {
|
|
215
|
+
drop({ key: 'tierGroups', reason: 'unreadable' });
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const groups = [];
|
|
219
|
+
groupNames = new Set();
|
|
220
|
+
for (const raw of m.tierGroups) {
|
|
221
|
+
const g = asRec(raw);
|
|
222
|
+
const name = g ? asStr(g.name) : undefined;
|
|
223
|
+
if (!g || !name || !TIER_GROUP_NAME.test(name)) {
|
|
224
|
+
drop({ key: 'tierGroups', ...(name ? { at: name } : {}), reason: 'unreadable' });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (groupNames.has(name)) {
|
|
228
|
+
drop({ key: 'tierGroups', at: name, reason: 'duplicate' });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
groupNames.add(name);
|
|
232
|
+
const tiers = {};
|
|
233
|
+
if (Object.hasOwn(g, 'tiers')) {
|
|
234
|
+
const t = asRec(g.tiers);
|
|
235
|
+
if (!t)
|
|
236
|
+
drop({ key: 'tierGroups', at: `${name}.tiers`, reason: 'unreadable' });
|
|
237
|
+
else {
|
|
238
|
+
for (const [tier, model] of Object.entries(t)) {
|
|
239
|
+
if (!TIER_WORDS.includes(tier))
|
|
240
|
+
drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: 'unsupported' });
|
|
241
|
+
else if (typeof model === 'string' && names.has(model))
|
|
242
|
+
setOwn(tiers, tier, model);
|
|
243
|
+
else
|
|
244
|
+
drop({ key: 'tierGroups', at: `${name}.${tier}`, ...refOf(model), reason: reasonOf(model) });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
groups.push({ name, tiers, ...(typeof g.notes === 'string' ? { notes: g.notes } : {}) });
|
|
249
|
+
}
|
|
250
|
+
doc.tierGroups = groups;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (accept && Object.hasOwn(m, 'activeTierGroup')) {
|
|
254
|
+
const a = m.activeTierGroup;
|
|
255
|
+
if (typeof a === 'string' && groupNames?.has(a))
|
|
256
|
+
doc.activeTierGroup = a;
|
|
257
|
+
else
|
|
258
|
+
drop({ key: 'activeTierGroup', ...refOf(a), reason: typeof a === 'string' && a !== '' ? (rawGroupNames.has(a) ? 'rejected' : 'dangling') : 'unreadable' });
|
|
259
|
+
}
|
|
260
|
+
return { doc, issues };
|
|
261
|
+
}
|
|
262
|
+
export function cloudModelsToDoc(models, opts) {
|
|
263
|
+
return modelsDocOf(models, opts).doc;
|
|
130
264
|
}
|
|
131
265
|
export function cloudModelsDropped(models) {
|
|
132
266
|
const m = asRec(models);
|
|
@@ -184,7 +318,136 @@ export function cloudRosterNames(rostersValue) {
|
|
|
184
318
|
.map((e) => asStr(asRec(e)?.name))
|
|
185
319
|
.filter((n) => !!n);
|
|
186
320
|
}
|
|
187
|
-
|
|
321
|
+
const BUDGET_NUMBER_KEYS = ['maxBudgetUsd', 'tpmLimit', 'rpmLimit', 'maxParallelRequests', 'maxRunCostUsd', 'maxIterations'];
|
|
322
|
+
const BUDGET_STRING_KEYS = ['mode', 'budgetDuration', 'enforcement'];
|
|
323
|
+
const PER_MODEL_NUMBER_KEYS = ['maxBudgetUsd', 'tpmLimit', 'rpmLimit'];
|
|
324
|
+
const _budgetKeysCovered = true;
|
|
325
|
+
const _budgetKeysReal = true;
|
|
326
|
+
const _perModelKeysCovered = true;
|
|
327
|
+
void _budgetKeysCovered;
|
|
328
|
+
void _budgetKeysReal;
|
|
329
|
+
void _perModelKeysCovered;
|
|
330
|
+
const isFiniteNum = (v) => typeof v === 'number' && Number.isFinite(v);
|
|
331
|
+
const ownOf = (o, k) => (Object.hasOwn(o, k) ? o[k] : undefined);
|
|
332
|
+
const inList = (list, k) => list.includes(k);
|
|
333
|
+
const viewModeOf = (b) => (!Object.hasOwn(b, 'previewOf') ? 'self' : asStr(b.previewOf) ? 'preview' : 'unknown');
|
|
334
|
+
const nullReadingOf = (mode) => mode === 'self' ? { kind: 'not-configured' } : mode === 'preview' ? { kind: 'redacted' } : { kind: 'unknown' };
|
|
335
|
+
function effectiveWarningsOf(b) {
|
|
336
|
+
const raw = ownOf(b, 'warnings');
|
|
337
|
+
if (!Array.isArray(raw))
|
|
338
|
+
return { kind: 'unknown' };
|
|
339
|
+
const warnings = [];
|
|
340
|
+
let unreadable = 0;
|
|
341
|
+
for (const row of raw) {
|
|
342
|
+
const w = asRec(row);
|
|
343
|
+
const kind = w ? asStr(ownOf(w, 'kind')) : undefined;
|
|
344
|
+
if (!w || !kind) {
|
|
345
|
+
unreadable += 1;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const out = { kind };
|
|
349
|
+
const domain = asStr(ownOf(w, 'domain'));
|
|
350
|
+
if (domain)
|
|
351
|
+
out.domain = domain;
|
|
352
|
+
for (const k of ['droppedNames', 'keys']) {
|
|
353
|
+
const xs = ownOf(w, k);
|
|
354
|
+
if (Array.isArray(xs)) {
|
|
355
|
+
const names = xs.filter((x) => typeof x === 'string' && x.length > 0);
|
|
356
|
+
if (names.length > 0)
|
|
357
|
+
out[k] = names;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const k of ['path', 'file', 'why']) {
|
|
361
|
+
const s = asStr(ownOf(w, k));
|
|
362
|
+
if (s)
|
|
363
|
+
out[k] = s;
|
|
364
|
+
}
|
|
365
|
+
const tf = ownOf(w, 'truncatedFrom');
|
|
366
|
+
if (isFiniteNum(tf))
|
|
367
|
+
out.truncatedFrom = tf;
|
|
368
|
+
warnings.push(out);
|
|
369
|
+
}
|
|
370
|
+
return { kind: 'reported', warnings, unreadable };
|
|
371
|
+
}
|
|
372
|
+
function budgetOf(b, mode) {
|
|
373
|
+
if (!Object.hasOwn(b, 'budget'))
|
|
374
|
+
return { kind: 'unknown' };
|
|
375
|
+
const v = b.budget;
|
|
376
|
+
if (v === null)
|
|
377
|
+
return nullReadingOf(mode);
|
|
378
|
+
const r = asRec(v);
|
|
379
|
+
if (!r)
|
|
380
|
+
return { kind: 'unknown' };
|
|
381
|
+
const limits = {};
|
|
382
|
+
const uninterpreted = [];
|
|
383
|
+
for (const [k, val] of Object.entries(r)) {
|
|
384
|
+
if (inList(BUDGET_NUMBER_KEYS, k)) {
|
|
385
|
+
if (isFiniteNum(val))
|
|
386
|
+
limits[k] = val;
|
|
387
|
+
else
|
|
388
|
+
uninterpreted.push(k);
|
|
389
|
+
}
|
|
390
|
+
else if (inList(BUDGET_STRING_KEYS, k)) {
|
|
391
|
+
const s = asStr(val);
|
|
392
|
+
if (s)
|
|
393
|
+
limits[k] = s;
|
|
394
|
+
else
|
|
395
|
+
uninterpreted.push(k);
|
|
396
|
+
}
|
|
397
|
+
else if (k === 'perModel') {
|
|
398
|
+
const pm = asRec(val);
|
|
399
|
+
if (!pm) {
|
|
400
|
+
uninterpreted.push(k);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const perModel = {};
|
|
404
|
+
for (const [model, entry] of Object.entries(pm)) {
|
|
405
|
+
const e = asRec(entry);
|
|
406
|
+
if (!e) {
|
|
407
|
+
uninterpreted.push(`perModel.${model}`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const one = {};
|
|
411
|
+
let unread = 0;
|
|
412
|
+
for (const [k2, v2] of Object.entries(e)) {
|
|
413
|
+
if (inList(PER_MODEL_NUMBER_KEYS, k2) && isFiniteNum(v2))
|
|
414
|
+
one[k2] = v2;
|
|
415
|
+
else {
|
|
416
|
+
uninterpreted.push(`perModel.${model}.${k2}`);
|
|
417
|
+
unread += 1;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (Object.keys(one).length > 0 || unread === 0)
|
|
421
|
+
setOwn(perModel, model, one);
|
|
422
|
+
}
|
|
423
|
+
limits.perModel = perModel;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
uninterpreted.push(k);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return { kind: 'configured', limits, uninterpretedKeys: uninterpreted };
|
|
430
|
+
}
|
|
431
|
+
function runtimeCapsOf(b, mode) {
|
|
432
|
+
if (!Object.hasOwn(b, 'runtimeCaps'))
|
|
433
|
+
return { kind: 'unknown' };
|
|
434
|
+
const v = b.runtimeCaps;
|
|
435
|
+
if (v === null)
|
|
436
|
+
return nullReadingOf(mode);
|
|
437
|
+
const r = asRec(v);
|
|
438
|
+
if (!r)
|
|
439
|
+
return { kind: 'unknown' };
|
|
440
|
+
const caps = {};
|
|
441
|
+
const uninterpreted = [];
|
|
442
|
+
for (const [k, val] of Object.entries(r)) {
|
|
443
|
+
if (typeof val === 'boolean')
|
|
444
|
+
setOwn(caps, k, val);
|
|
445
|
+
else
|
|
446
|
+
uninterpreted.push(k);
|
|
447
|
+
}
|
|
448
|
+
return { kind: 'configured', caps, uninterpretedKeys: uninterpreted };
|
|
449
|
+
}
|
|
450
|
+
export function projectEffectiveBody(body, skillBodies, env, rostersValue, opts) {
|
|
188
451
|
const b = asRec(body) ?? {};
|
|
189
452
|
const config = asRec(b.config) ?? {};
|
|
190
453
|
const mcpDomain = asRec(config.mcp);
|
|
@@ -202,7 +465,8 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue) {
|
|
|
202
465
|
return name && hash && skillBodies[hash] === undefined ? name : undefined;
|
|
203
466
|
})
|
|
204
467
|
.filter((n) => !!n);
|
|
205
|
-
const modelsDoc =
|
|
468
|
+
const { doc: modelsDoc, issues: modelRefIssues } = modelsDocOf(config.models, opts);
|
|
469
|
+
const view = viewModeOf(b);
|
|
206
470
|
return {
|
|
207
471
|
mcp,
|
|
208
472
|
skills,
|
|
@@ -215,5 +479,138 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue) {
|
|
|
215
479
|
droppedMcpServers,
|
|
216
480
|
droppedModels: cloudModelsDropped(config.models),
|
|
217
481
|
execution: cloudExecutionToPolicy(config.execution),
|
|
482
|
+
effectiveWarnings: effectiveWarningsOf(b),
|
|
483
|
+
budget: budgetOf(b, view),
|
|
484
|
+
runtimeCaps: runtimeCapsOf(b, view),
|
|
485
|
+
modelRefIssues,
|
|
218
486
|
};
|
|
219
487
|
}
|
|
488
|
+
const quoted = (s, fallback) => (s === undefined ? fallback : `the "${s}" settings`);
|
|
489
|
+
const listOf = (xs, truncatedFrom) => xs === undefined ? '' : `: ${xs.join(', ')}${truncatedFrom === undefined ? '' : ` (${truncatedFrom} in total)`}`;
|
|
490
|
+
function warningNotice(w) {
|
|
491
|
+
const where = quoted(w.domain, 'the configuration');
|
|
492
|
+
switch (w.kind) {
|
|
493
|
+
case 'hosts-grandfathered':
|
|
494
|
+
return { level: 'warn', text: `cloud config warning: host entries exceeding the current limits were skipped by the registry${listOf(w.droppedNames, w.truncatedFrom)}` };
|
|
495
|
+
case 'domain-defaulted':
|
|
496
|
+
return { level: 'warn', text: `cloud config warning: ${where} could not be read by the registry and were served as defaults — what is configured there is not in effect` };
|
|
497
|
+
case 'entry-dropped':
|
|
498
|
+
return { level: 'warn', text: `cloud config warning: an entry in ${where} could not be read by the registry and was left out${w.path === undefined ? '' : ` (${w.path})`}` };
|
|
499
|
+
case 'unknown-keys-dropped':
|
|
500
|
+
return { level: 'warn', text: `cloud config warning: the registry dropped unrecognised keys from ${where}${listOf(w.keys, w.truncatedFrom)}` };
|
|
501
|
+
case 'unknown-keys-carried':
|
|
502
|
+
return { level: 'warn', text: `cloud config warning: ${where} carry keys the registry does not recognise, passed on as written (check the spelling)${listOf(w.keys, w.truncatedFrom)}` };
|
|
503
|
+
case 'legacy-governance-lifted':
|
|
504
|
+
return { level: 'info', text: `cloud config note: ${where} were taken from their older location${listOf(w.keys, undefined)}` };
|
|
505
|
+
case 'unread-config-file':
|
|
506
|
+
return { level: 'warn', text: `cloud config warning: a configuration file was not read by the registry${w.file === undefined ? '' : ` (${w.file}${w.why === undefined ? '' : `: ${w.why}`})`}` };
|
|
507
|
+
default:
|
|
508
|
+
return { level: 'warn', text: `cloud config warning: the registry reported "${w.kind}"${w.domain === undefined ? '' : ` for ${where}`} — this client does not recognise that warning, so the served configuration may be degraded` };
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function droppedRefNotice(d) {
|
|
512
|
+
const t = d.target;
|
|
513
|
+
const notServed = d.reason === 'rejected'
|
|
514
|
+
? 'is in the catalog, but its entry is one the local engine cannot use (incomplete or invalid settings)'
|
|
515
|
+
: 'is not in the model catalog served to you';
|
|
516
|
+
switch (d.key) {
|
|
517
|
+
case 'models':
|
|
518
|
+
return { level: 'warn', text: `cloud config: ${t === undefined ? 'a model entry' : `model "${t}"`} is in the catalog, but its entry is one the local engine cannot use (incomplete or invalid settings) — it was left out of the local catalog` };
|
|
519
|
+
case 'default':
|
|
520
|
+
return { level: 'warn', text: `cloud config: ${t === undefined ? 'the default model setting could not be read' : `the default model "${t}" ${notServed}`}, so it was left out — the local engine picks its default from roles and tiers instead` };
|
|
521
|
+
case 'roles':
|
|
522
|
+
return { level: 'warn', text: `cloud config: role "${d.at ?? ''}" ${t === undefined ? 'names no readable model' : `points at model "${t}", which ${notServed}`} — the role was left out and falls back along the engine's role chain` };
|
|
523
|
+
case 'atModelAllowlist':
|
|
524
|
+
return { level: 'warn', text: `cloud config: ${t === undefined ? 'an unreadable @-mention allowlist entry' : `@-mention allowlist entry "${t}" ${notServed} and`} was left out` };
|
|
525
|
+
case 'tierGroups':
|
|
526
|
+
if (d.reason === 'duplicate')
|
|
527
|
+
return { level: 'warn', text: `cloud config: tier group "${d.at ?? ''}" appears more than once — only the first one is kept` };
|
|
528
|
+
if (d.reason === 'dangling' || d.reason === 'rejected')
|
|
529
|
+
return { level: 'warn', text: `cloud config: tier "${d.at ?? ''}" is bound to model "${t ?? ''}", which ${notServed} — that tier was left unbound` };
|
|
530
|
+
if (d.reason === 'unsupported')
|
|
531
|
+
return { level: 'warn', text: `cloud config: tier "${d.at ?? ''}" is not a tier the local engine reads (${TIER_WORDS.join(', ')}) — that binding was left out` };
|
|
532
|
+
return { level: 'warn', text: `cloud config: a tier group setting${d.at === undefined ? '' : ` ("${d.at}")`} could not be read and was left out` };
|
|
533
|
+
case 'activeTierGroup':
|
|
534
|
+
return { level: 'warn', text: `cloud config: ${t === undefined ? 'the active tier group setting could not be read' : `the active tier group "${t}" is not among the readable tier groups`} — no tier table is active on the local engine` };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function budgetNotice(r) {
|
|
538
|
+
switch (r.kind) {
|
|
539
|
+
case 'configured': {
|
|
540
|
+
const l = r.limits;
|
|
541
|
+
const parts = [];
|
|
542
|
+
for (const k of ['mode', 'maxBudgetUsd', 'budgetDuration', 'tpmLimit', 'rpmLimit', 'maxParallelRequests', 'maxRunCostUsd', 'maxIterations', 'enforcement']) {
|
|
543
|
+
if (l[k] !== undefined)
|
|
544
|
+
parts.push(`${k} ${String(l[k])}`);
|
|
545
|
+
}
|
|
546
|
+
for (const [model, one] of Object.entries(l.perModel ?? {})) {
|
|
547
|
+
const inner = PER_MODEL_NUMBER_KEYS.filter((k) => one[k] !== undefined).map((k) => `${k} ${String(one[k])}`);
|
|
548
|
+
parts.push(`perModel ${model}: ${inner.length > 0 ? inner.join(', ') : 'no limits set'}`);
|
|
549
|
+
}
|
|
550
|
+
const extra = r.uninterpretedKeys.length > 0 ? `also carries ${r.uninterpretedKeys.join(', ')} (not shown by this client)` : '';
|
|
551
|
+
const body = parts.length > 0 ? parts.join(' · ') + (extra ? ` · ${extra}` : '') : extra ? `configured — ${extra}` : 'configured, with no limits set';
|
|
552
|
+
return { level: 'info', text: `cloud budget: ${body}` };
|
|
553
|
+
}
|
|
554
|
+
case 'not-configured':
|
|
555
|
+
return { level: 'info', text: "cloud budget: none set for you in the organisation's entitlement settings on this registry (pooled budgets and token quotas are separate readings)" };
|
|
556
|
+
case 'redacted':
|
|
557
|
+
return { level: 'info', text: 'cloud budget: withheld — this response previews another principal, and budget values are not shown in previews' };
|
|
558
|
+
case 'unknown':
|
|
559
|
+
return { level: 'info', text: 'cloud budget: not reported in a readable form by this registry — unknown, which is not the same as having no budget' };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function runtimeCapsNotice(r) {
|
|
563
|
+
switch (r.kind) {
|
|
564
|
+
case 'configured': {
|
|
565
|
+
const parts = Object.entries(r.caps).map(([k, v]) => `${k}=${String(v)}`);
|
|
566
|
+
const extra = r.uninterpretedKeys.length > 0 ? `also carries ${r.uninterpretedKeys.join(', ')} (not shown by this client)` : '';
|
|
567
|
+
const body = parts.length > 0 ? parts.join(', ') + (extra ? ` · ${extra}` : '') : extra ? `configured — ${extra}` : 'configured, with no caps set';
|
|
568
|
+
return { level: 'info', text: `cloud runtime caps: ${body}` };
|
|
569
|
+
}
|
|
570
|
+
case 'not-configured':
|
|
571
|
+
return { level: 'info', text: 'cloud runtime caps: none set for you on this registry — local controls decide' };
|
|
572
|
+
case 'redacted':
|
|
573
|
+
return { level: 'info', text: 'cloud runtime caps: withheld — this response previews another principal, and cap values are not shown in previews' };
|
|
574
|
+
case 'unknown':
|
|
575
|
+
return { level: 'info', text: 'cloud runtime caps: not reported in a readable form by this registry — unknown, which is not the same as having no caps' };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
export function cloudEffectiveNotices(p) {
|
|
579
|
+
const out = [];
|
|
580
|
+
const w = p.effectiveWarnings;
|
|
581
|
+
if (w.kind === 'unknown') {
|
|
582
|
+
out.push({ level: 'info', text: 'cloud config: this registry does not report configuration degradation warnings — whether the served configuration was degraded is unknown' });
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
for (const one of w.warnings)
|
|
586
|
+
out.push(warningNotice(one));
|
|
587
|
+
if (w.unreadable > 0) {
|
|
588
|
+
out.push({ level: 'warn', text: `cloud config warning: ${w.unreadable} warning ${w.unreadable === 1 ? 'entry' : 'entries'} from the registry could not be read by this client — the served configuration may be degraded` });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const d of p.modelRefIssues)
|
|
592
|
+
if (d.outcome === 'dropped')
|
|
593
|
+
out.push(droppedRefNotice(d));
|
|
594
|
+
const keptModels = p.modelRefIssues.filter((d) => d.outcome === 'kept' && d.key === 'models');
|
|
595
|
+
if (keptModels.length > 0) {
|
|
596
|
+
const named = keptModels.flatMap((d) => (d.target === undefined ? [] : [`"${d.target}"`]));
|
|
597
|
+
const shown = named.slice(0, 5).join(', ') + (named.length > 5 ? `, +${named.length - 5} more` : '');
|
|
598
|
+
out.push({
|
|
599
|
+
level: 'warn',
|
|
600
|
+
text: `cloud config: none of the model entries served to you${shown !== '' ? ` (${shown})` : ''} is one the local engine can use (incomplete or invalid settings) — ` +
|
|
601
|
+
'the catalog is kept as sent, and a locally started engine rejects the cloud model settings: at startup it uses its own environment catalog instead, and on a later refresh it keeps the previous configuration',
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const keptAllowlist = p.modelRefIssues.filter((d) => d.outcome === 'kept' && d.key === 'atModelAllowlist');
|
|
605
|
+
if (keptAllowlist.length > 0) {
|
|
606
|
+
const names = keptAllowlist.flatMap((d) => (d.target === undefined ? [] : [`"${d.target}"`]));
|
|
607
|
+
out.push({
|
|
608
|
+
level: 'warn',
|
|
609
|
+
text: `cloud config: no @-mention allowlist entry${names.length > 0 ? ` (${names.join(', ')})` : ''} names a usable model in the catalog served to you — ` +
|
|
610
|
+
'the list is kept as sent, because an empty list would let every model be mentioned; with it, a locally started engine rejects the cloud model settings — at startup it uses its own environment catalog instead, and on a later refresh it keeps the previous configuration',
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
out.push(budgetNotice(p.budget));
|
|
614
|
+
out.push(runtimeCapsNotice(p.runtimeCaps));
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
2
|
-
import type { HitlClientLike } from './hitlBridge.js';
|
|
2
|
+
import type { DecideOperationObserver, HitlClientLike } from './hitlBridge.js';
|
|
3
3
|
import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts, type ToolApprovalFrameOutcome } from './toolApprovalWire.js';
|
|
4
4
|
import type { GateLedger } from './gateLedger.js';
|
|
5
5
|
export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
@@ -22,6 +22,7 @@ export interface AskGateWireDeps {
|
|
|
22
22
|
onParkReattach?: (e: ParkReattachNotice) => void;
|
|
23
23
|
approvalLane?: ToolApprovalFrameLaneOpts;
|
|
24
24
|
onToolApprovalOutcome?: (frame: ToolApprovalFrame, outcome: ToolApprovalFrameOutcome) => void | Promise<void>;
|
|
25
|
+
onDecideOperation?: DecideOperationObserver;
|
|
25
26
|
}
|
|
26
27
|
export type GateKind = 'ask' | 'fs';
|
|
27
28
|
export interface GatePark {
|
|
@@ -64,6 +64,29 @@ export declare class DecideTransportRetryExhaustedError extends Error {
|
|
|
64
64
|
readonly attempts: number;
|
|
65
65
|
constructor(attempts: number, lastFailureText: string);
|
|
66
66
|
}
|
|
67
|
+
export interface DecideOperationIdentity {
|
|
68
|
+
readonly operation: object;
|
|
69
|
+
readonly taskId: string;
|
|
70
|
+
readonly sessionId: string;
|
|
71
|
+
readonly boundCallId?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface DecideOperationStart extends DecideOperationIdentity {
|
|
74
|
+
readonly phase: 'start';
|
|
75
|
+
}
|
|
76
|
+
export interface DecideOperationEnd extends DecideOperationIdentity {
|
|
77
|
+
readonly phase: 'end';
|
|
78
|
+
readonly ok: boolean;
|
|
79
|
+
readonly attempts: number;
|
|
80
|
+
readonly retryExhausted: boolean;
|
|
81
|
+
}
|
|
82
|
+
export type DecideOperationEvent = DecideOperationStart | DecideOperationEnd;
|
|
83
|
+
export type DecideOperationObserver = (ev: DecideOperationEvent) => void | Promise<void>;
|
|
84
|
+
export interface HitlBridgeOptions {
|
|
85
|
+
onDecideOperation?: DecideOperationObserver;
|
|
86
|
+
}
|
|
87
|
+
export interface DecideOperationSpan {
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
67
90
|
export interface ActiveGate {
|
|
68
91
|
gate: CheckpointGate;
|
|
69
92
|
seq?: string | undefined;
|
|
@@ -85,7 +108,8 @@ export declare class HitlBridge {
|
|
|
85
108
|
private readonly client;
|
|
86
109
|
private readonly taskId;
|
|
87
110
|
private active;
|
|
88
|
-
|
|
111
|
+
private readonly decideObserver;
|
|
112
|
+
constructor(client: HitlClientLike, taskId: string, opts?: HitlBridgeOptions);
|
|
89
113
|
observe(ev: AgentEvent): void;
|
|
90
114
|
currentGate(): CheckpointGate | null;
|
|
91
115
|
currentGateReopen(): SuspendedReopenReading | null;
|
|
@@ -97,6 +121,7 @@ export declare class HitlBridge {
|
|
|
97
121
|
remember?: 'session' | undefined;
|
|
98
122
|
} | HitlDecideDenyOutcome, toolUseID?: string, opts?: {
|
|
99
123
|
signal?: AbortSignal;
|
|
124
|
+
operation?: DecideOperationSpan;
|
|
100
125
|
}, preResolvedPending?: PendingCheckpoint): Promise<unknown>;
|
|
101
126
|
answerQuestion(answers: AskAnswer[], toolUseID?: string, opts?: {
|
|
102
127
|
signal?: AbortSignal;
|
|
@@ -108,6 +133,9 @@ export declare class HitlBridge {
|
|
|
108
133
|
signal?: AbortSignal;
|
|
109
134
|
}): Promise<AssistantTaskStatus>;
|
|
110
135
|
private decideRaw;
|
|
136
|
+
private decideIdentityOf;
|
|
137
|
+
openDecideOperation(): DecideOperationSpan;
|
|
138
|
+
private decideRawAttempts;
|
|
111
139
|
}
|
|
112
140
|
export type GatePrompt<D extends HitlPermissionDecisionLike = HitlPermissionDecisionLike> = (ctx: {
|
|
113
141
|
toolName: string;
|