@sema-agent/client-core 0.82.0 → 0.82.2
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 +33 -0
- package/README.md +10 -7
- package/dist/adapt/toolCards.d.ts +15 -0
- package/dist/adapt/toolCards.js +34 -19
- package/dist/adapter/activeRunSelfHeal.d.ts +3 -0
- package/dist/adapter/activeRunSelfHeal.js +37 -7
- package/dist/adapter/downstream/eventToSdkMessage.js +7 -4
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
- package/dist/adapter/downstream/terminalToSdkResult.js +44 -5
- package/dist/adapter/runStream.js +33 -4
- package/dist/adapter/types.d.ts +2 -0
- package/dist/agentsWireCaps.d.ts +1 -0
- package/dist/agentsWireCaps.js +110 -29
- package/dist/cloudConfigWireCaps.d.ts +77 -2
- package/dist/cloudConfigWireCaps.js +405 -8
- package/dist/decideFailureNote.d.ts +1 -0
- package/dist/decideFailureNote.js +8 -0
- package/dist/engineNoticeCodes.js +2 -0
- package/dist/executionLaneCapability.js +3 -0
- package/dist/fileHistoryCaptureCapability.d.ts +18 -0
- package/dist/fileHistoryCaptureCapability.js +58 -0
- package/dist/gateOutcome.js +2 -0
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- 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/hitlHostSurface.js +2 -3
- package/dist/hitl/parkResolver.d.ts +1 -0
- package/dist/hitl/parkResolver.js +17 -9
- package/dist/hitl/planReviewWire.d.ts +6 -0
- package/dist/hitl/planReviewWire.js +8 -2
- package/dist/hitl/toolApprovalWire.d.ts +2 -1
- package/dist/hitl/toolApprovalWire.js +105 -102
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/modelIdentityResolvability.d.ts +31 -0
- package/dist/modelIdentityResolvability.js +145 -0
- package/dist/notifications.d.ts +3 -1
- package/dist/notifications.js +18 -2
- package/dist/printToolResultFrame.js +15 -1
- package/dist/resumeRefusalCopy.d.ts +12 -0
- package/dist/resumeRefusalCopy.js +45 -1
- package/dist/seam.d.ts +2 -0
- package/dist/toolResult.d.ts +2 -2
- package/dist/toolResult.js +7 -6
- package/docs/INTEGRATION-CLIENTS.md +168 -11
- package/package.json +3 -3
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function decideFailureNote(err: unknown): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { corruptStoredRowContent, corruptStoredRowWhere } from './wireErrorTriage.js';
|
|
2
|
+
import { resumePolicyBlockContent, resumePolicyBlockFromError } from './resumeRefusalCopy.js';
|
|
3
|
+
export function decideFailureNote(err) {
|
|
4
|
+
const where = corruptStoredRowWhere(err);
|
|
5
|
+
const fold = resumePolicyBlockFromError(err);
|
|
6
|
+
return ((where === undefined ? '' : ` — ${corruptStoredRowContent(where)}`) +
|
|
7
|
+
(fold === null ? '' : ` — ${resumePolicyBlockContent(fold)}`));
|
|
8
|
+
}
|
|
@@ -55,6 +55,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
|
|
|
55
55
|
'route.fallback_to_primary',
|
|
56
56
|
'route.classifier_unroutable',
|
|
57
57
|
'route.base_url_changed_key_unchanged',
|
|
58
|
+
'route.response_model_mismatch',
|
|
58
59
|
'task.user_steer_undrained',
|
|
59
60
|
'task.user_followup_undrained',
|
|
60
61
|
'steering.parked_input_blocked',
|
|
@@ -129,6 +130,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
|
|
|
129
130
|
'route.fallback_to_primary': 'operator',
|
|
130
131
|
'route.classifier_unroutable': 'operator',
|
|
131
132
|
'route.base_url_changed_key_unchanged': 'operator',
|
|
133
|
+
'route.response_model_mismatch': 'operator',
|
|
132
134
|
'task.user_steer_undrained': 'user',
|
|
133
135
|
'task.user_followup_undrained': 'user',
|
|
134
136
|
'steering.parked_input_blocked': 'user',
|
|
@@ -51,6 +51,9 @@ export function executionLaneDoctorDetail(reading) {
|
|
|
51
51
|
if (reading.view.toolsOnThisHost) {
|
|
52
52
|
return `execution lane ${lane} — tools run on the engine host (same filesystem), so skills that have a directory of their own can carry a baseDir`;
|
|
53
53
|
}
|
|
54
|
+
if (reading.view.provider === 'in-process') {
|
|
55
|
+
return 'no execution lane on this deployment (in-process) — the engine has no machine of its own for running tools here, so its built-in file and shell tools are not mounted and skill baseDir is never sent; whether a given run had tools is answered by its tool roster';
|
|
56
|
+
}
|
|
54
57
|
return `execution lane ${lane} — tools run on a different machine than the engine, so skill baseDir is never sent on this deployment`;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FileHistoryCaptureReading = {
|
|
2
|
+
kind: 'unobserved';
|
|
3
|
+
} | {
|
|
4
|
+
kind: 'not_reported';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'present';
|
|
7
|
+
word: string;
|
|
8
|
+
};
|
|
9
|
+
export type FileHistoryCaptureMode = 'off' | 'on-always' | 'unknown';
|
|
10
|
+
export declare function projectFileHistoryCaptureCapability(caps: unknown): FileHistoryCaptureReading | undefined;
|
|
11
|
+
export declare function noteEngineCapsForFileHistoryCapture(baseUrl: string, caps: unknown, opts?: {
|
|
12
|
+
generation?: number;
|
|
13
|
+
}): void;
|
|
14
|
+
export declare function observedFileHistoryCapture(baseUrl?: string | undefined): FileHistoryCaptureReading;
|
|
15
|
+
export declare function fileHistoryCaptureMode(reading: FileHistoryCaptureReading): FileHistoryCaptureMode;
|
|
16
|
+
export declare function fileHistoryCaptureDoctorDetail(reading: FileHistoryCaptureReading): string;
|
|
17
|
+
export declare function forgetFileHistoryCaptureReading(baseUrl: string | undefined): void;
|
|
18
|
+
export declare function __resetFileHistoryCaptureReadingsForTests(): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { capForDisplay } from './fleetTaskDesc.js';
|
|
2
|
+
import { engineWireTarget } from './engineWireTarget.js';
|
|
3
|
+
import { createEngineCapReader } from './engineCapReader.js';
|
|
4
|
+
export function projectFileHistoryCaptureCapability(caps) {
|
|
5
|
+
if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Object.hasOwn(caps, 'fileHistoryCapture'))
|
|
8
|
+
return { kind: 'not_reported' };
|
|
9
|
+
const v = caps.fileHistoryCapture;
|
|
10
|
+
if (v === undefined)
|
|
11
|
+
return { kind: 'not_reported' };
|
|
12
|
+
if (typeof v !== 'string' || v.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
return { kind: 'present', word: v };
|
|
15
|
+
}
|
|
16
|
+
const reader = createEngineCapReader({
|
|
17
|
+
name: 'fileHistoryCapture',
|
|
18
|
+
project: projectFileHistoryCaptureCapability,
|
|
19
|
+
makeUnobserved: () => ({ kind: 'unobserved' }),
|
|
20
|
+
});
|
|
21
|
+
export function noteEngineCapsForFileHistoryCapture(baseUrl, caps, opts) {
|
|
22
|
+
reader.note(baseUrl, caps, opts);
|
|
23
|
+
}
|
|
24
|
+
export function observedFileHistoryCapture(baseUrl = engineWireTarget()?.baseUrl) {
|
|
25
|
+
return reader.observed(baseUrl);
|
|
26
|
+
}
|
|
27
|
+
export function fileHistoryCaptureMode(reading) {
|
|
28
|
+
if (reading.kind !== 'present')
|
|
29
|
+
return 'unknown';
|
|
30
|
+
if (reading.word === 'off')
|
|
31
|
+
return 'off';
|
|
32
|
+
if (reading.word === 'on-always')
|
|
33
|
+
return 'on-always';
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
const WORD_DETAIL_MAX = 40;
|
|
37
|
+
export function fileHistoryCaptureDoctorDetail(reading) {
|
|
38
|
+
switch (reading.kind) {
|
|
39
|
+
case 'unobserved':
|
|
40
|
+
return 'file history capture not observed — the engine reports it on /v1/capabilities; this process has no usable capabilities reading cached for it, so whether this deployment captures file history is unknown';
|
|
41
|
+
case 'not_reported':
|
|
42
|
+
return 'file history capture not reported by this engine — only newer engines advertise it; this does not say whether file history is captured here';
|
|
43
|
+
case 'present':
|
|
44
|
+
if (reading.word === 'off') {
|
|
45
|
+
return 'file history capture off on this deployment — the engine takes no snapshots of the files it changes; whether code can be rewound here is answered separately by the rewind readings';
|
|
46
|
+
}
|
|
47
|
+
if (reading.word === 'on-always') {
|
|
48
|
+
return 'file history capture always on — the engine snapshots files before changing them in every session, and it cannot be turned off for a single session here';
|
|
49
|
+
}
|
|
50
|
+
return `file history capture reported as "${capForDisplay(reading.word, WORD_DETAIL_MAX)}" — a mode this client does not know yet, so whether it captures is shown as unknown`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function forgetFileHistoryCaptureReading(baseUrl) {
|
|
54
|
+
reader.forget(baseUrl);
|
|
55
|
+
}
|
|
56
|
+
export function __resetFileHistoryCaptureReadingsForTests() {
|
|
57
|
+
reader.__resetForTests();
|
|
58
|
+
}
|
package/dist/gateOutcome.js
CHANGED
|
@@ -146,6 +146,8 @@ export function ccToolDenialKindForToolEnd(frame) {
|
|
|
146
146
|
const stamped = frame._sema_denial_kind;
|
|
147
147
|
if (isCcToolDenialKind(stamped))
|
|
148
148
|
return stamped;
|
|
149
|
+
if (frame._sema_denial_unclassified === true)
|
|
150
|
+
return undefined;
|
|
149
151
|
const outcome = gateOutcomeOf(frame);
|
|
150
152
|
const kind = outcome?.settlement?.kind;
|
|
151
153
|
if (kind === 'human_refused')
|
|
@@ -8,7 +8,7 @@ export type { AskAnsweredOutput } from './gateLedger.js';
|
|
|
8
8
|
export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
|
|
9
9
|
export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
|
|
10
10
|
export type { GateDenyAttribution } from './gateLedger.js';
|
|
11
|
-
export { toAnsweredOutput, isAlreadyResolvedGateReason, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
11
|
+
export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
12
12
|
export type { HopBudget, HopRound } from './parkResolver.js';
|
|
13
13
|
export type { GateOutcome, GateFailureCode } from './parkResolver.js';
|
|
14
14
|
export declare function bridgeAskUserQuestionGates(source: AsyncGenerator<AgentEvent>, deps: AskGateWireDeps, opts?: {
|
package/dist/hitl/askGateWire.js
CHANGED
|
@@ -6,7 +6,7 @@ export { HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE } from './frameRouter.js';
|
|
|
6
6
|
export { HITL_POLICY_DENY_MESSAGE } from './frameRouter.js';
|
|
7
7
|
export { SEMA_COLLATERAL_ABORT_KEY } from './gateLedger.js';
|
|
8
8
|
export { SEMA_DENIAL_KIND_KEY } from './gateLedger.js';
|
|
9
|
-
export { toAnsweredOutput, isAlreadyResolvedGateReason, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
9
|
+
export { toAnsweredOutput, isAlreadyResolvedGateReason, isGateStandingErrorCode, GATE_FAILURE_CODES, nextHopBudget, MAX_GATE_HOPS, MAX_TOTAL_PARKS } from './parkResolver.js';
|
|
10
10
|
export async function* bridgeAskUserQuestionGates(source, deps, opts) {
|
|
11
11
|
const led = createGateLedger();
|
|
12
12
|
const taskId = { current: opts?.taskId };
|
|
@@ -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 {
|