@sema-agent/client-core 0.85.2 → 0.86.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/CHANGELOG.md +208 -0
- package/README.md +20 -13
- package/dist/adapt/arms.js +5 -1
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +3 -3
- package/dist/agentSession/backgroundView.js +7 -1
- package/dist/agentSession/contract.d.ts +1 -1
- package/dist/centerWiringCapability.d.ts +38 -0
- package/dist/centerWiringCapability.js +202 -0
- package/dist/decideFailureNote.js +3 -0
- package/dist/engineCapReader.d.ts +8 -0
- package/dist/engineCapReader.js +15 -0
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +8 -0
- package/dist/engineNoticeCodes.d.ts +62 -0
- package/dist/engineNoticeCodes.js +247 -29
- package/dist/executionLaneCapability.d.ts +6 -0
- package/dist/executionLaneCapability.js +55 -2
- package/dist/fleet/fleetProjection.js +6 -8
- package/dist/fleet/workflowSizeWarning.js +2 -5
- package/dist/gateOutcome.d.ts +13 -0
- package/dist/gateOutcome.js +48 -1
- package/dist/gateVocabulary.d.ts +6 -3
- package/dist/gateVocabulary.js +7 -15
- package/dist/generated/engineFactTables.d.ts +10 -0
- package/dist/generated/engineFactTables.js +113 -0
- package/dist/generated/engineNoticeTables.js +14 -0
- package/dist/hitl/refusedToolNameProse.d.ts +4 -0
- package/dist/hitl/refusedToolNameProse.js +7 -0
- package/dist/hitl/sessionPolicyDeliverable.js +9 -5
- package/dist/hitl/sessionPolicyWire.d.ts +16 -0
- package/dist/hitl/sessionPolicyWire.js +49 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/inheritEnvWire.d.ts +22 -0
- package/dist/inheritEnvWire.js +84 -0
- package/dist/peerFrames.js +2 -10
- package/dist/printInitToolFace.d.ts +6 -0
- package/dist/printInitToolFace.js +115 -2
- package/dist/readCredentialRefusal.d.ts +2 -0
- package/dist/readCredentialRefusal.js +7 -0
- package/dist/request/taskRequest.d.ts +4 -5
- package/dist/request/taskRequest.js +33 -23
- package/dist/runTerminal.js +2 -6
- package/dist/sqlEngineCapability.js +15 -2
- package/dist/storePostureCapability.d.ts +25 -0
- package/dist/storePostureCapability.js +132 -0
- package/dist/toolResult.js +2 -47
- package/dist/wireErrorTriage.d.ts +1 -0
- package/dist/wireErrorTriage.js +3 -0
- package/dist/wireFailureShape.d.ts +3 -0
- package/dist/wireFailureShape.js +44 -0
- package/dist/wireRefusalCopy.d.ts +39 -1
- package/dist/wireRefusalCopy.js +350 -2
- package/dist/workflowClient.d.ts +4 -0
- package/dist/workflowClient.js +12 -4
- package/docs/INTEGRATION-CLIENTS.md +716 -14
- package/package.json +2 -2
|
@@ -3,10 +3,13 @@ interface WireFailureShape {
|
|
|
3
3
|
errorCode?: string;
|
|
4
4
|
retryAfterSec?: number;
|
|
5
5
|
committed?: 'no' | 'unknown' | 'unreadable';
|
|
6
|
+
bodyField?: string;
|
|
7
|
+
bodyName?: string;
|
|
6
8
|
message: string;
|
|
7
9
|
}
|
|
8
10
|
export declare function readErrorNumber(o: object, key: string): number | undefined;
|
|
9
11
|
export declare function readErrorString(o: object, key: string): string | undefined;
|
|
10
12
|
export declare function readErrorStringList(o: object, key: string): readonly string[] | undefined;
|
|
13
|
+
export declare function readErrorStrictStringList(o: object, key: string): readonly string[] | undefined;
|
|
11
14
|
export declare function wireFailureShapeOf(e: unknown): WireFailureShape;
|
|
12
15
|
export {};
|
package/dist/wireFailureShape.js
CHANGED
|
@@ -52,6 +52,33 @@ export function readErrorStringList(o, key) {
|
|
|
52
52
|
const v = readErrorSlot(o, key, (x) => Array.isArray(x));
|
|
53
53
|
return Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined;
|
|
54
54
|
}
|
|
55
|
+
export function readErrorStrictStringList(o, key) {
|
|
56
|
+
const strict = (x) => {
|
|
57
|
+
if (!Array.isArray(x))
|
|
58
|
+
return false;
|
|
59
|
+
for (let i = 0; i < x.length; i++)
|
|
60
|
+
if (typeof x[i] !== 'string')
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
const v = readErrorSlot(o, key, strict);
|
|
65
|
+
if (!Array.isArray(v))
|
|
66
|
+
return undefined;
|
|
67
|
+
try {
|
|
68
|
+
const out = [];
|
|
69
|
+
const len = v.length;
|
|
70
|
+
for (let i = 0; i < len; i++) {
|
|
71
|
+
const item = v[i];
|
|
72
|
+
if (typeof item !== 'string')
|
|
73
|
+
return undefined;
|
|
74
|
+
out.push(item);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
55
82
|
export function wireFailureShapeOf(e) {
|
|
56
83
|
const o = e ?? {};
|
|
57
84
|
const status = readGuarded(o, 'status');
|
|
@@ -85,6 +112,21 @@ export function wireFailureShapeOf(e) {
|
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
114
|
}
|
|
115
|
+
const ownString = (key) => {
|
|
116
|
+
if (extraObj === undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
try {
|
|
119
|
+
if (!Object.hasOwn(extraObj, key))
|
|
120
|
+
return undefined;
|
|
121
|
+
const v = extraObj[key];
|
|
122
|
+
return typeof v === 'string' ? v : undefined;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const bodyField = ownString('field');
|
|
129
|
+
const bodyName = ownString('name');
|
|
88
130
|
let message;
|
|
89
131
|
try {
|
|
90
132
|
message = typeof rawMessage === 'string' && rawMessage !== '' ? rawMessage : String(e);
|
|
@@ -107,6 +149,8 @@ export function wireFailureShapeOf(e) {
|
|
|
107
149
|
...(typeof errorCode === 'string' ? { errorCode } : {}),
|
|
108
150
|
...(sec !== undefined ? { retryAfterSec: sec } : {}),
|
|
109
151
|
...(committed !== undefined ? { committed } : {}),
|
|
152
|
+
...(bodyField !== undefined ? { bodyField } : {}),
|
|
153
|
+
...(bodyName !== undefined ? { bodyName } : {}),
|
|
110
154
|
message,
|
|
111
155
|
};
|
|
112
156
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { USAGE_WINDOW_EXHAUSTED } from './engineErrorCodes.js';
|
|
1
|
+
import { FLEET_LEASE_UNAVAILABLE, USAGE_WINDOW_EXHAUSTED } from './engineErrorCodes.js';
|
|
2
|
+
import type { TaskRequestLike } from './request/taskRequest.js';
|
|
2
3
|
export declare const CONFLICT_APPROVAL_SETTLED = "conflict.approval_settled";
|
|
3
4
|
export declare const CONFLICT_RUN_NOT_RUNNING = "conflict.run_not_running";
|
|
4
5
|
export type CancelConflictKind = 'approval_settled' | 'run_not_running' | 'unrecognized';
|
|
@@ -14,6 +15,13 @@ export interface UsageWindowExhaustedDetail {
|
|
|
14
15
|
}
|
|
15
16
|
export declare function usageWindowExhaustedFromError(err: unknown): UsageWindowExhaustedDetail | null;
|
|
16
17
|
export declare function usageWindowExhaustedContent(detail: UsageWindowExhaustedDetail): string;
|
|
18
|
+
export interface FleetLeaseUnavailableDetail {
|
|
19
|
+
code: typeof FLEET_LEASE_UNAVAILABLE;
|
|
20
|
+
reason?: string;
|
|
21
|
+
retryAfterSec?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function fleetLeaseUnavailableFromError(err: unknown): FleetLeaseUnavailableDetail | null;
|
|
24
|
+
export declare function fleetLeaseUnavailableContent(detail: FleetLeaseUnavailableDetail): string;
|
|
17
25
|
export type DenyAttributionRefusalKind = 'not_in_proof' | 'decision_conflict' | 'attribution_rejected';
|
|
18
26
|
export interface DenyAttributionRefusalDetail {
|
|
19
27
|
kind: DenyAttributionRefusalKind;
|
|
@@ -33,3 +41,33 @@ export interface SessionDeleteConflictDetail {
|
|
|
33
41
|
}
|
|
34
42
|
export declare function sessionDeleteConflictFromError(err: unknown): SessionDeleteConflictDetail | null;
|
|
35
43
|
export declare function sessionDeleteConflictContent(detail: Pick<SessionDeleteConflictDetail, 'kind' | 'remaining' | 'activeTaskId'>): string;
|
|
44
|
+
export type SettingsPermissionBucket = 'allow' | 'deny' | 'ask';
|
|
45
|
+
export type SettingsPermissionBucketRefusalReason = 'over_cap' | 'bad_entry';
|
|
46
|
+
export interface SettingsPermissionBucketRefusalDetail {
|
|
47
|
+
readonly bucket: SettingsPermissionBucket;
|
|
48
|
+
readonly reason: SettingsPermissionBucketRefusalReason;
|
|
49
|
+
readonly count?: number;
|
|
50
|
+
readonly cap?: number;
|
|
51
|
+
readonly index?: number;
|
|
52
|
+
readonly folded?: true;
|
|
53
|
+
}
|
|
54
|
+
export declare function settingsPermissionBucketRefusalFromError(err: unknown): SettingsPermissionBucketRefusalDetail | null;
|
|
55
|
+
export declare function settingsPermissionBucketRefusalContent(detail: SettingsPermissionBucketRefusalDetail): string;
|
|
56
|
+
export interface ToolDeclarationRefusalDetail {
|
|
57
|
+
readonly code: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function toolDeclarationRefusalFromError(err: unknown): ToolDeclarationRefusalDetail | null;
|
|
60
|
+
export declare function toolDeclarationRefusalContent(_detail: Pick<ToolDeclarationRefusalDetail, 'code'>): string;
|
|
61
|
+
export interface BodyShapeRefusalDetail {
|
|
62
|
+
readonly unknownKeys?: readonly string[];
|
|
63
|
+
readonly unsupportedKeys?: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
export declare function bodyShapeRefusalFromError(err: unknown): BodyShapeRefusalDetail | null;
|
|
66
|
+
export declare function bodyShapeRefusalTouchesSettings(detail: BodyShapeRefusalDetail | null | undefined, sentBody?: TaskRequestLike): boolean | undefined;
|
|
67
|
+
export interface HostPostureRefusalDetail {
|
|
68
|
+
readonly code: string;
|
|
69
|
+
readonly askedWholeEnvironment?: true;
|
|
70
|
+
readonly askedOpenWriteFace?: true;
|
|
71
|
+
}
|
|
72
|
+
export declare function hostPostureRefusalFromError(err: unknown, sentRequest?: TaskRequestLike): HostPostureRefusalDetail | null;
|
|
73
|
+
export declare function hostPostureRefusalContent(detail: Pick<HostPostureRefusalDetail, 'askedWholeEnvironment' | 'askedOpenWriteFace'>): string;
|
package/dist/wireRefusalCopy.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { readErrorNumber, readErrorString, readErrorStringList } from './wireFailureShape.js';
|
|
2
|
-
import { ACTIVE_RUN_BUSY_ERROR_CODE, USAGE_WINDOW_EXHAUSTED } from './engineErrorCodes.js';
|
|
1
|
+
import { readErrorNumber, readErrorStrictStringList, readErrorString, readErrorStringList } from './wireFailureShape.js';
|
|
2
|
+
import { ACTIVE_RUN_BUSY_ERROR_CODE, FLEET_LEASE_UNAVAILABLE, RESUME_POLICY_FOLD_CODE, USAGE_WINDOW_EXHAUSTED } from './engineErrorCodes.js';
|
|
3
3
|
import { capForDisplay } from './fleetTaskDesc.js';
|
|
4
|
+
import { effectiveWireErrorCode } from './wireErrorTriage.js';
|
|
5
|
+
import { CORE_TOOL_DECLARATION_CODES } from './generated/engineFactTables.js';
|
|
4
6
|
export const CONFLICT_APPROVAL_SETTLED = 'conflict.approval_settled';
|
|
5
7
|
export const CONFLICT_RUN_NOT_RUNNING = 'conflict.run_not_running';
|
|
6
8
|
function readStringField(o, key) {
|
|
@@ -84,6 +86,64 @@ export function usageWindowExhaustedContent(detail) {
|
|
|
84
86
|
? `${head} · Send it again in about ${humanWait(detail.retryAfterSec)}`
|
|
85
87
|
: `${head} · The engine did not say how long the window needs — send it again a little later`;
|
|
86
88
|
}
|
|
89
|
+
const positiveFinite = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
90
|
+
export function fleetLeaseUnavailableFromError(err) {
|
|
91
|
+
if (err === null || typeof err !== 'object')
|
|
92
|
+
return null;
|
|
93
|
+
let code;
|
|
94
|
+
try {
|
|
95
|
+
code = err.errorCode;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (code !== FLEET_LEASE_UNAVAILABLE)
|
|
101
|
+
return null;
|
|
102
|
+
const reason = readErrorString(err, 'reason');
|
|
103
|
+
const rawSec = readErrorNumber(err, 'retryAfterSec');
|
|
104
|
+
let sec = positiveFinite(rawSec) ? Math.ceil(rawSec) : undefined;
|
|
105
|
+
if (sec === undefined) {
|
|
106
|
+
let rawMs;
|
|
107
|
+
try {
|
|
108
|
+
rawMs = err.retryAfterMs;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
rawMs = undefined;
|
|
112
|
+
}
|
|
113
|
+
sec = positiveFinite(rawMs) ? Math.ceil(rawMs / 1000) : undefined;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
code: FLEET_LEASE_UNAVAILABLE,
|
|
117
|
+
...(reason !== undefined && reason !== '' ? { reason } : {}),
|
|
118
|
+
...(sec !== undefined ? { retryAfterSec: sec } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function fleetLeaseUnavailableContent(detail) {
|
|
122
|
+
let reason;
|
|
123
|
+
let sec;
|
|
124
|
+
try {
|
|
125
|
+
reason = typeof detail === 'object' && detail !== null ? detail.reason : undefined;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
reason = undefined;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
sec = typeof detail === 'object' && detail !== null ? detail.retryAfterSec : undefined;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
sec = undefined;
|
|
135
|
+
}
|
|
136
|
+
const head = "This deployment could not confirm your usage budget with its quota service, and it refuses billed work while it cannot check, so the engine did not accept this request · Nothing is wrong with the request itself";
|
|
137
|
+
const cause = reason === 'auth-rejected'
|
|
138
|
+
? " · The quota service rejected this engine's credentials — if this keeps happening, whoever operates the engine needs to fix them"
|
|
139
|
+
: reason === 'unreachable'
|
|
140
|
+
? ' · The engine could not reach the quota service'
|
|
141
|
+
: '';
|
|
142
|
+
const wait = positiveFinite(sec)
|
|
143
|
+
? ` · Send it again in about ${humanWait(Math.ceil(sec))}`
|
|
144
|
+
: ' · The engine did not say how long to wait — send it again a little later';
|
|
145
|
+
return `${head}${cause}${wait}`;
|
|
146
|
+
}
|
|
87
147
|
const SETTLED_BY_NOT_IN_PROOF = 'settled_by_not_in_proof';
|
|
88
148
|
const REQUEST_FIELD_CONFLICT = 'request.field_conflict';
|
|
89
149
|
const RESUME_OUTCOME_INVALID = 'resume_outcome_invalid';
|
|
@@ -211,3 +271,291 @@ export function sessionDeleteConflictContent(detail) {
|
|
|
211
271
|
return sessionDeleteConflictUnrecognizedContent();
|
|
212
272
|
}
|
|
213
273
|
}
|
|
274
|
+
const FIELD_INVALID = 'request.field_invalid';
|
|
275
|
+
const BUCKET_OF_FIELD = Object.freeze({
|
|
276
|
+
'settings.permissions.allow': 'allow',
|
|
277
|
+
'settings.permissions.deny': 'deny',
|
|
278
|
+
'settings.permissions.ask': 'ask',
|
|
279
|
+
});
|
|
280
|
+
const nonNegInt = (v) => (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 ? v : undefined);
|
|
281
|
+
const posInt = (v) => (typeof v === 'number' && Number.isSafeInteger(v) && v >= 1 ? v : undefined);
|
|
282
|
+
export function settingsPermissionBucketRefusalFromError(err) {
|
|
283
|
+
try {
|
|
284
|
+
if (typeof err !== 'object' || err === null)
|
|
285
|
+
return null;
|
|
286
|
+
if (effectiveWireErrorCode(err) !== FIELD_INVALID)
|
|
287
|
+
return null;
|
|
288
|
+
let outer;
|
|
289
|
+
try {
|
|
290
|
+
outer = err.errorCode;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const field = readErrorString(err, 'field');
|
|
296
|
+
const bucket = field !== undefined && Object.hasOwn(BUCKET_OF_FIELD, field) ? BUCKET_OF_FIELD[field] : undefined;
|
|
297
|
+
if (bucket === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const reason = readErrorString(err, 'reason');
|
|
300
|
+
if (reason !== 'over_cap' && reason !== 'bad_entry')
|
|
301
|
+
return null;
|
|
302
|
+
const folded = outer === RESUME_POLICY_FOLD_CODE;
|
|
303
|
+
if (reason === 'over_cap') {
|
|
304
|
+
const count = nonNegInt(readErrorNumber(err, 'count'));
|
|
305
|
+
const cap = posInt(readErrorNumber(err, 'cap'));
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
bucket,
|
|
308
|
+
reason,
|
|
309
|
+
...(count !== undefined ? { count } : {}),
|
|
310
|
+
...(cap !== undefined ? { cap } : {}),
|
|
311
|
+
...(folded ? { folded: true } : {}),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const index = nonNegInt(readErrorNumber(err, 'index'));
|
|
315
|
+
return Object.freeze({ bucket, reason, ...(index !== undefined ? { index } : {}), ...(folded ? { folded: true } : {}) });
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const BUCKET_REFUSAL_UNKNOWN = "The engine refused the permission rules in this request's settings as too many or malformed · Nothing was trimmed or dropped — fix the rule lists, then send the request again";
|
|
322
|
+
export function settingsPermissionBucketRefusalContent(detail) {
|
|
323
|
+
try {
|
|
324
|
+
if (typeof detail !== 'object' || detail === null)
|
|
325
|
+
return BUCKET_REFUSAL_UNKNOWN;
|
|
326
|
+
const bucket = detail.bucket;
|
|
327
|
+
const reason = detail.reason;
|
|
328
|
+
if (bucket !== 'allow' && bucket !== 'deny' && bucket !== 'ask')
|
|
329
|
+
return BUCKET_REFUSAL_UNKNOWN;
|
|
330
|
+
if (reason !== 'over_cap' && reason !== 'bad_entry')
|
|
331
|
+
return BUCKET_REFUSAL_UNKNOWN;
|
|
332
|
+
const folded = detail.folded === true;
|
|
333
|
+
const subject = folded ? 'The engine refused to continue this parked work: in the request stored for it,' : 'The engine refused this request:';
|
|
334
|
+
const list = `the ${bucket} rule list in the permission settings (permissions.${bucket})`;
|
|
335
|
+
let what;
|
|
336
|
+
let fix;
|
|
337
|
+
if (reason === 'over_cap') {
|
|
338
|
+
const count = nonNegInt(detail.count);
|
|
339
|
+
const cap = posInt(detail.cap);
|
|
340
|
+
what =
|
|
341
|
+
count !== undefined && cap !== undefined
|
|
342
|
+
? count > cap
|
|
343
|
+
? `${list} has ${count} entries, ${count - cap} more than the ${cap} the engine accepts`
|
|
344
|
+
: `${list} has ${count} entries, and the engine accepts at most ${cap}`
|
|
345
|
+
: `${list} has more entries than the engine accepts`;
|
|
346
|
+
fix = 'merge rules into broader patterns or remove some';
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
const index = nonNegInt(detail.index);
|
|
350
|
+
what = index !== undefined ? `entry ${index + 1} of ${list} is empty or not text` : `${list} is not a list of rules`;
|
|
351
|
+
fix = index !== undefined ? 'fix or remove that entry, so every entry is a non-empty rule' : 'make it a list of non-empty rules';
|
|
352
|
+
}
|
|
353
|
+
const next = folded
|
|
354
|
+
? `It cannot continue as stored, and the engine refuses the list rather than silently losing rules — ${fix}, then submit the work again as a new request`
|
|
355
|
+
: `Nothing was trimmed or dropped, because the engine refuses the list rather than silently losing rules — ${fix}, then send the request again`;
|
|
356
|
+
return `${subject} ${what} · ${next}`;
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return BUCKET_REFUSAL_UNKNOWN;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
export function toolDeclarationRefusalFromError(err) {
|
|
363
|
+
try {
|
|
364
|
+
if (typeof err !== 'object' || err === null)
|
|
365
|
+
return null;
|
|
366
|
+
let code;
|
|
367
|
+
try {
|
|
368
|
+
code = err.errorCode;
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
if (typeof code !== 'string' || !CORE_TOOL_DECLARATION_CODES.includes(code))
|
|
374
|
+
return null;
|
|
375
|
+
return Object.freeze({ code });
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const TOOL_DECLARATION_REFUSAL = "The engine refused to start this run because of a tool this request declares; the engine's own message says which tool and what is wrong with it · " +
|
|
382
|
+
'This is a problem with the request itself, not a temporary outage — sending the same request again is refused the same way; fix or remove that tool declaration, then send it';
|
|
383
|
+
export function toolDeclarationRefusalContent(_detail) {
|
|
384
|
+
return TOOL_DECLARATION_REFUSAL;
|
|
385
|
+
}
|
|
386
|
+
const BODY_SHAPE = 'request.body_shape';
|
|
387
|
+
const SETTINGS_KEY_PREFIX = 'settings.';
|
|
388
|
+
export function bodyShapeRefusalFromError(err) {
|
|
389
|
+
try {
|
|
390
|
+
if (typeof err !== 'object' || err === null)
|
|
391
|
+
return null;
|
|
392
|
+
if (effectiveWireErrorCode(err) !== BODY_SHAPE)
|
|
393
|
+
return null;
|
|
394
|
+
const unknownKeys = readErrorStrictStringList(err, 'unknownKeys');
|
|
395
|
+
const unsupportedKeys = readErrorStrictStringList(err, 'unsupportedKeys');
|
|
396
|
+
return Object.freeze({
|
|
397
|
+
...(unknownKeys !== undefined ? { unknownKeys: Object.freeze(unknownKeys) } : {}),
|
|
398
|
+
...(unsupportedKeys !== undefined ? { unsupportedKeys: Object.freeze(unsupportedKeys) } : {}),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const SETTINGS_PERMISSIONS_PREFIX = 'permissions.';
|
|
406
|
+
const ECHO_MAX_CHARS = 64;
|
|
407
|
+
function echoedKeyName(key) {
|
|
408
|
+
const clean = key.replace(/[\u0000-\u001f\u007f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, '');
|
|
409
|
+
return clean.length > ECHO_MAX_CHARS ? `${clean.slice(0, ECHO_MAX_CHARS)}…` : clean;
|
|
410
|
+
}
|
|
411
|
+
function hasCustomJson(v) {
|
|
412
|
+
return typeof v === 'object' && v !== null && typeof v.toJSON === 'function';
|
|
413
|
+
}
|
|
414
|
+
function sentValue(bag, key) {
|
|
415
|
+
try {
|
|
416
|
+
if (!Object.prototype.propertyIsEnumerable.call(bag, key))
|
|
417
|
+
return { state: 'absent' };
|
|
418
|
+
const value = bag[key];
|
|
419
|
+
if (value === undefined || typeof value === 'function' || typeof value === 'symbol')
|
|
420
|
+
return { state: 'absent' };
|
|
421
|
+
if (typeof value === 'bigint' || hasCustomJson(value))
|
|
422
|
+
return { state: 'unknown' };
|
|
423
|
+
return { state: 'sent', value };
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return { state: 'unknown' };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function isKeyBag(v) {
|
|
430
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
431
|
+
}
|
|
432
|
+
function echoMatch(bag, prefix, echo) {
|
|
433
|
+
let names;
|
|
434
|
+
try {
|
|
435
|
+
names = Object.keys(bag);
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
return 'unknown';
|
|
439
|
+
}
|
|
440
|
+
let state = 'absent';
|
|
441
|
+
for (const name of names) {
|
|
442
|
+
if (prefix + echoedKeyName(name) !== echo)
|
|
443
|
+
continue;
|
|
444
|
+
const s = sentValue(bag, name).state;
|
|
445
|
+
if (s === 'sent')
|
|
446
|
+
return 'sent';
|
|
447
|
+
if (s === 'unknown')
|
|
448
|
+
state = 'unknown';
|
|
449
|
+
}
|
|
450
|
+
return state;
|
|
451
|
+
}
|
|
452
|
+
function settingsLayerMatch(body, echo) {
|
|
453
|
+
const s = sentValue(body, 'settings');
|
|
454
|
+
if (s.state !== 'sent')
|
|
455
|
+
return s.state;
|
|
456
|
+
if (!isKeyBag(s.value))
|
|
457
|
+
return 'absent';
|
|
458
|
+
let state = echoMatch(s.value, SETTINGS_KEY_PREFIX, echo);
|
|
459
|
+
if (state === 'sent' || !echo.startsWith(SETTINGS_KEY_PREFIX + SETTINGS_PERMISSIONS_PREFIX))
|
|
460
|
+
return state;
|
|
461
|
+
const p = sentValue(s.value, 'permissions');
|
|
462
|
+
if (p.state === 'unknown')
|
|
463
|
+
return 'unknown';
|
|
464
|
+
if (p.state === 'sent' && isKeyBag(p.value)) {
|
|
465
|
+
const inner = echoMatch(p.value, SETTINGS_KEY_PREFIX + SETTINGS_PERMISSIONS_PREFIX, echo);
|
|
466
|
+
if (inner !== 'absent')
|
|
467
|
+
state = inner;
|
|
468
|
+
}
|
|
469
|
+
return state;
|
|
470
|
+
}
|
|
471
|
+
export function bodyShapeRefusalTouchesSettings(detail, sentBody) {
|
|
472
|
+
try {
|
|
473
|
+
if (typeof detail !== 'object' || detail === null)
|
|
474
|
+
return undefined;
|
|
475
|
+
let bothRead = true;
|
|
476
|
+
const prefixed = [];
|
|
477
|
+
for (const list of [detail.unknownKeys, detail.unsupportedKeys]) {
|
|
478
|
+
if (!Array.isArray(list)) {
|
|
479
|
+
bothRead = false;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
for (const k of list) {
|
|
483
|
+
if (typeof k !== 'string')
|
|
484
|
+
continue;
|
|
485
|
+
if (!k.startsWith(SETTINGS_KEY_PREFIX))
|
|
486
|
+
return false;
|
|
487
|
+
prefixed.push(k);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (prefixed.length === 0)
|
|
491
|
+
return bothRead ? false : undefined;
|
|
492
|
+
const body = sentBody;
|
|
493
|
+
if (!isKeyBag(body) || hasCustomJson(body))
|
|
494
|
+
return undefined;
|
|
495
|
+
const top = prefixed.map((k) => echoMatch(body, '', k));
|
|
496
|
+
if (top.includes('sent'))
|
|
497
|
+
return false;
|
|
498
|
+
for (let i = 0; i < prefixed.length; i++) {
|
|
499
|
+
if (top[i] === 'absent' && settingsLayerMatch(body, prefixed[i]) === 'sent')
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const HOST_POSTURE_REFUSED = 'request.host_posture_refused';
|
|
509
|
+
const BYPASS_PERMISSIONS = 'bypassPermissions';
|
|
510
|
+
function readProp(o, key) {
|
|
511
|
+
if (typeof o !== 'object' || o === null)
|
|
512
|
+
return undefined;
|
|
513
|
+
try {
|
|
514
|
+
return o[key];
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
export function hostPostureRefusalFromError(err, sentRequest) {
|
|
521
|
+
try {
|
|
522
|
+
if (readProp(err, 'errorCode') !== HOST_POSTURE_REFUSED)
|
|
523
|
+
return null;
|
|
524
|
+
const askedWholeEnvironment = readProp(sentRequest, 'inheritEnv') === 'all';
|
|
525
|
+
const askedOpenWriteFace = readProp(sentRequest, 'permissionMode') === BYPASS_PERMISSIONS ||
|
|
526
|
+
readProp(readProp(readProp(sentRequest, 'settings'), 'permissions'), 'defaultMode') === BYPASS_PERMISSIONS;
|
|
527
|
+
return Object.freeze({
|
|
528
|
+
code: HOST_POSTURE_REFUSED,
|
|
529
|
+
...(askedWholeEnvironment ? { askedWholeEnvironment: true } : {}),
|
|
530
|
+
...(askedOpenWriteFace ? { askedOpenWriteFace: true } : {}),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const HOST_POSTURE_NOT_LOCAL = 'This is not a problem with your local settings and not something this client can grant — whoever runs this engine decides it';
|
|
538
|
+
const HOST_POSTURE_WHOLE_ENV = "The engine refused this request before anything ran: this deployment serves more than one user, so a request may not hand the shell this machine's whole environment · " +
|
|
539
|
+
`${HOST_POSTURE_NOT_LOCAL}; send it again without the whole-environment setting (or with the default), and the shell gets the default environment, with credential-like variables removed`;
|
|
540
|
+
const HOST_POSTURE_WRITE_FACE = 'The engine refused this request before anything ran: this deployment does not let a request lift the write boundary with bypassPermissions · ' +
|
|
541
|
+
`${HOST_POSTURE_NOT_LOCAL}; send it again with another permission mode`;
|
|
542
|
+
const HOST_POSTURE_BOTH = "The engine refused this request before anything ran: the request asked both for the shell to inherit this machine's whole environment and for bypassPermissions, and this deployment refused at least one of them; the refusal does not say which · " +
|
|
543
|
+
`${HOST_POSTURE_NOT_LOCAL}; send it again without one or both (the whole-environment setting is honored only on a single-user deployment)`;
|
|
544
|
+
const HOST_POSTURE_UNATTRIBUTED = "The engine refused this request before anything ran: this deployment does not accept a setting in it that only a single-user deployment may use; the engine's own message names it · " +
|
|
545
|
+
`${HOST_POSTURE_NOT_LOCAL}; send it again without that setting`;
|
|
546
|
+
export function hostPostureRefusalContent(detail) {
|
|
547
|
+
try {
|
|
548
|
+
const env = readProp(detail, 'askedWholeEnvironment') === true;
|
|
549
|
+
const write = readProp(detail, 'askedOpenWriteFace') === true;
|
|
550
|
+
if (env && write)
|
|
551
|
+
return HOST_POSTURE_BOTH;
|
|
552
|
+
if (env)
|
|
553
|
+
return HOST_POSTURE_WHOLE_ENV;
|
|
554
|
+
if (write)
|
|
555
|
+
return HOST_POSTURE_WRITE_FACE;
|
|
556
|
+
return HOST_POSTURE_UNATTRIBUTED;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return HOST_POSTURE_UNATTRIBUTED;
|
|
560
|
+
}
|
|
561
|
+
}
|
package/dist/workflowClient.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export type WorkflowResolution = {
|
|
|
33
33
|
state: WorkflowRunState;
|
|
34
34
|
code: 501 | 404;
|
|
35
35
|
lastError: string | null;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'unauthorized';
|
|
38
|
+
state: WorkflowRunState | null;
|
|
39
|
+
lastError: string | null;
|
|
36
40
|
};
|
|
37
41
|
export interface LiveWorkflowConfig {
|
|
38
42
|
baseUrl: string;
|
package/dist/workflowClient.js
CHANGED
|
@@ -4,6 +4,7 @@ import { engineSessionParamSpread } from './engineSessionParam.js';
|
|
|
4
4
|
import { makeEngineWireClient } from './engineWireSdk.js';
|
|
5
5
|
import { engineWireDebugEnabled } from './engineWireTarget.js';
|
|
6
6
|
import { hostLog } from './host.js';
|
|
7
|
+
import { isReadCredentialRefusal } from './readCredentialRefusal.js';
|
|
7
8
|
function coerceRunStatus(s) {
|
|
8
9
|
switch (s) {
|
|
9
10
|
case 'completed':
|
|
@@ -306,6 +307,7 @@ export function readWorkflowResumeAdmissionIncomplete(run) {
|
|
|
306
307
|
const NO_RUN_BACKOFF_MS = 3000;
|
|
307
308
|
const DEGRADED_BACKOFF_MS = 3000;
|
|
308
309
|
const UNEXPECTED_ERROR_BACKOFF_MS = 3000;
|
|
310
|
+
const UNAUTHORIZED_BACKOFF_MS = 60_000;
|
|
309
311
|
const STREAM_DROP_BACKOFF_MS = 1000;
|
|
310
312
|
const REFRESH_THROTTLE_MS = 400;
|
|
311
313
|
const MAX_WORKFLOW_ACTIVITY_CALLS = 40;
|
|
@@ -744,6 +746,7 @@ export function createLiveWorkflowSource(config) {
|
|
|
744
746
|
let lastGetAt = 0;
|
|
745
747
|
let first = true;
|
|
746
748
|
let phase = 'loop';
|
|
749
|
+
let unauthorized = false;
|
|
747
750
|
let activityDisabled = false;
|
|
748
751
|
const listeners = new Set();
|
|
749
752
|
function notify() {
|
|
@@ -772,6 +775,7 @@ export function createLiveWorkflowSource(config) {
|
|
|
772
775
|
if (phase === 'empty')
|
|
773
776
|
phase = 'loop';
|
|
774
777
|
connected = false;
|
|
778
|
+
unauthorized = isReadCredentialRefusal(err);
|
|
775
779
|
if (err instanceof APIError) {
|
|
776
780
|
code = err.status;
|
|
777
781
|
degraded = err.status === 501 || err.status === 404;
|
|
@@ -812,6 +816,7 @@ export function createLiveWorkflowSource(config) {
|
|
|
812
816
|
runId = run.id;
|
|
813
817
|
connected = true;
|
|
814
818
|
degraded = false;
|
|
819
|
+
unauthorized = false;
|
|
815
820
|
code = null;
|
|
816
821
|
lastError = null;
|
|
817
822
|
if (engineWireDebugEnabled()) {
|
|
@@ -837,12 +842,13 @@ export function createLiveWorkflowSource(config) {
|
|
|
837
842
|
code = null;
|
|
838
843
|
phase = 'empty';
|
|
839
844
|
connected = false;
|
|
845
|
+
unauthorized = false;
|
|
840
846
|
await abortableSleep(NO_RUN_BACKOFF_MS, abort.signal);
|
|
841
847
|
continue;
|
|
842
848
|
}
|
|
843
849
|
await refresh(id, abort.signal);
|
|
844
|
-
if (degraded) {
|
|
845
|
-
await abortableSleep(DEGRADED_BACKOFF_MS, abort.signal);
|
|
850
|
+
if (degraded || unauthorized) {
|
|
851
|
+
await abortableSleep(unauthorized ? UNAUTHORIZED_BACKOFF_MS : DEGRADED_BACKOFF_MS, abort.signal);
|
|
846
852
|
continue;
|
|
847
853
|
}
|
|
848
854
|
if (!activityDisabled) {
|
|
@@ -881,7 +887,7 @@ export function createLiveWorkflowSource(config) {
|
|
|
881
887
|
connected = true;
|
|
882
888
|
if (Date.now() - lastGetAt >= REFRESH_THROTTLE_MS)
|
|
883
889
|
await refresh(id, abort.signal);
|
|
884
|
-
if (degraded)
|
|
890
|
+
if (degraded || unauthorized)
|
|
885
891
|
break;
|
|
886
892
|
if (beat === 'closed')
|
|
887
893
|
break;
|
|
@@ -904,7 +910,7 @@ export function createLiveWorkflowSource(config) {
|
|
|
904
910
|
if (disposed || abort.signal.aborted)
|
|
905
911
|
return;
|
|
906
912
|
noteDegrade(err);
|
|
907
|
-
await abortableSleep(UNEXPECTED_ERROR_BACKOFF_MS, abort.signal);
|
|
913
|
+
await abortableSleep(unauthorized ? UNAUTHORIZED_BACKOFF_MS : UNEXPECTED_ERROR_BACKOFF_MS, abort.signal);
|
|
908
914
|
}
|
|
909
915
|
}
|
|
910
916
|
}
|
|
@@ -944,6 +950,8 @@ export function createLiveWorkflowSource(config) {
|
|
|
944
950
|
resolution() {
|
|
945
951
|
if (phase === 'unavailable')
|
|
946
952
|
return { kind: 'unavailable', lastError };
|
|
953
|
+
if (unauthorized)
|
|
954
|
+
return { kind: 'unauthorized', state: current, lastError };
|
|
947
955
|
const hardCode = degraded && (code === 501 || code === 404) ? code : null;
|
|
948
956
|
if (current !== null)
|
|
949
957
|
return hardCode !== null ? { kind: 'stale', state: current, code: hardCode, lastError } : { kind: 'live', state: current };
|