@sema-agent/client-core 0.81.1 → 0.82.1
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 +40 -0
- package/README.md +12 -8
- 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 +42 -9
- 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/decideFailureNote.d.ts +1 -0
- package/dist/decideFailureNote.js +8 -0
- package/dist/engineErrorCodes.d.ts +1 -0
- package/dist/engineErrorCodes.js +1 -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 +22 -2
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- package/dist/hitl/hitlHostSurface.js +6 -4
- package/dist/hitl/parkResolver.d.ts +1 -0
- package/dist/hitl/parkResolver.js +11 -2
- package/dist/hitl/planReviewWire.js +14 -1
- package/dist/hitl/toolApprovalWire.js +3 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/modelIdentityResolvability.d.ts +31 -0
- package/dist/modelIdentityResolvability.js +145 -0
- package/dist/printToolResultFrame.js +15 -1
- package/dist/registryQuotaUsage.d.ts +44 -0
- package/dist/registryQuotaUsage.js +111 -0
- package/dist/resumeRefusalCopy.d.ts +12 -0
- package/dist/resumeRefusalCopy.js +45 -1
- package/dist/rewindArchiveCapability.d.ts +24 -0
- package/dist/rewindArchiveCapability.js +125 -0
- package/dist/seam.d.ts +2 -0
- package/dist/toolResult.d.ts +2 -2
- package/dist/toolResult.js +7 -6
- package/dist/toolRoster.d.ts +12 -0
- package/dist/toolRoster.js +35 -0
- package/dist/wireErrorTriage.d.ts +4 -0
- package/dist/wireErrorTriage.js +59 -5
- package/dist/wireFailureShape.js +27 -22
- package/docs/INTEGRATION-CLIENTS.md +192 -13
- package/package.json +3 -3
|
@@ -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')
|
|
@@ -156,11 +158,29 @@ export function ccToolDenialKindForToolEnd(frame) {
|
|
|
156
158
|
const settlementAbsent = typeof rawGate === 'object' && rawGate !== null && rawGate.settlement === undefined;
|
|
157
159
|
if (outcome !== undefined && settlementAbsent && outcome.disposition.kind === 'denied') {
|
|
158
160
|
const by = outcome.disposition.deniedBy;
|
|
159
|
-
if (by
|
|
160
|
-
return
|
|
161
|
+
if (by === 'ask_resolution' || !isGateDeniedByWord(by))
|
|
162
|
+
return undefined;
|
|
163
|
+
if (DENIED_BY_MAY_CARRY_CAUSE.has(by)) {
|
|
164
|
+
const rawDisposition = rawKeyOf(rawGate, 'disposition');
|
|
165
|
+
if (rawKeyOf(rawDisposition, 'cause') !== undefined) {
|
|
166
|
+
const cause = outcome.disposition.cause;
|
|
167
|
+
if (cause === 'unavailable')
|
|
168
|
+
return 'automode-unavailable';
|
|
169
|
+
if (cause === 'parse_error')
|
|
170
|
+
return 'automode-parsing-error';
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (by === 'classifier')
|
|
174
|
+
return 'automode-blocked';
|
|
175
|
+
if (rawKeyOf(rawDisposition, 'classifier') !== undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return ccToolDenialKindForSettledBy('policy');
|
|
161
179
|
}
|
|
162
180
|
return undefined;
|
|
163
181
|
}
|
|
182
|
+
const rawKeyOf = (holder, key) => typeof holder === 'object' && holder !== null ? holder[key] : undefined;
|
|
183
|
+
const DENIED_BY_MAY_CARRY_CAUSE = new Set(['classifier', 'policy']);
|
|
164
184
|
const _gateOutcomeShapePin = (w) => w;
|
|
165
185
|
void _gateOutcomeShapePin;
|
|
166
186
|
const _settlementWordMirrorPin = SETTLEMENT_KIND_WORDS;
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
3
|
import { unrefTimer } from '../unrefTimer.js';
|
|
4
|
+
import { decideFailureNote } from '../decideFailureNote.js';
|
|
4
5
|
import { HitlSafetyError } from './hitlBridge.js';
|
|
5
6
|
const hostSurfaceByKey = createSessionSlot();
|
|
6
7
|
const hostSurfaceMissesByKey = new Map();
|
|
@@ -38,8 +39,8 @@ export const CANCEL_DENY_BUDGET_MS = 2000;
|
|
|
38
39
|
export const CANCEL_DENY_WARN_TEXT = 'could not cancel the pending question — the session may stay locked; the run may need engine-side recovery';
|
|
39
40
|
const CANCEL_DENY_WARN_KEY = 'hitl-cancel-deny-warn';
|
|
40
41
|
const CANCEL_DENY_WARN_TIMEOUT_MS = 10_000;
|
|
41
|
-
function surfaceCancelDenyWarn() {
|
|
42
|
-
surfaceSelfClearingWarn(CANCEL_DENY_WARN_KEY,
|
|
42
|
+
function surfaceCancelDenyWarn(text = CANCEL_DENY_WARN_TEXT) {
|
|
43
|
+
surfaceSelfClearingWarn(CANCEL_DENY_WARN_KEY, text, CANCEL_DENY_WARN_TIMEOUT_MS);
|
|
43
44
|
}
|
|
44
45
|
function surfaceSelfClearingWarn(key, text, timeoutMs) {
|
|
45
46
|
try {
|
|
@@ -112,8 +113,9 @@ export function observeCancelByDeny(settle, taskId) {
|
|
|
112
113
|
hostLog('debug', `[hitl-cancel-deny] cancel-by-deny no_pending for run ${taskId} (benign — already resolved)`);
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const rowNote = decideFailureNote(e);
|
|
117
|
+
hostLog('error', `[hitl-cancel-deny] cancel-by-deny FAILED for run ${taskId}: ${String(e)}${rowNote}`);
|
|
118
|
+
surfaceCancelDenyWarn(`${CANCEL_DENY_WARN_TEXT}${rowNote}`);
|
|
117
119
|
})
|
|
118
120
|
.finally(() => {
|
|
119
121
|
if (timer !== undefined)
|
|
@@ -33,6 +33,7 @@ export type GateOutcome = {
|
|
|
33
33
|
currentPending?: GateCurrentPending | undefined;
|
|
34
34
|
};
|
|
35
35
|
export declare function isAlreadyResolvedGateReason(reason: unknown): boolean;
|
|
36
|
+
export declare function isGateStandingErrorCode(code: string | null | undefined): boolean;
|
|
36
37
|
export declare function toAnsweredOutput(questions: unknown[], answer: QuestionAnswer): AskAnsweredOutput;
|
|
37
38
|
export type ParkResolution = {
|
|
38
39
|
kind: 'reattach';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { putOwnKey } from '../ownKey.js';
|
|
2
2
|
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
3
|
+
import { RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
|
|
3
4
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
4
5
|
import { hostLog } from '../host.js';
|
|
5
6
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -7,6 +8,7 @@ import { observeCancelByDeny } from './hitlHostSurface.js';
|
|
|
7
8
|
import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
|
|
8
9
|
import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
|
|
9
10
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
11
|
+
import { decideFailureNote } from '../decideFailureNote.js';
|
|
10
12
|
export const MAX_GATE_HOPS = 2;
|
|
11
13
|
export function nextHopBudget(prev, round) {
|
|
12
14
|
const total = (prev?.total ?? 0) + 1;
|
|
@@ -40,7 +42,14 @@ function alreadyResolvedReasonToken(reason) {
|
|
|
40
42
|
}
|
|
41
43
|
function isAlreadyResolvedFailure(outcome) {
|
|
42
44
|
const code = 'code' in outcome && isGateFailureCode(outcome.code) ? outcome.code : undefined;
|
|
43
|
-
|
|
45
|
+
if (code !== undefined)
|
|
46
|
+
return code === 'no_pending';
|
|
47
|
+
if ('errorCode' in outcome && isGateStandingErrorCode(outcome.errorCode))
|
|
48
|
+
return false;
|
|
49
|
+
return isAlreadyResolvedGateReason(outcome.reason);
|
|
50
|
+
}
|
|
51
|
+
export function isGateStandingErrorCode(code) {
|
|
52
|
+
return code === RESUME_POLICY_FOLD_CODE || code === 'corrupt';
|
|
44
53
|
}
|
|
45
54
|
export function toAnsweredOutput(questions, answer) {
|
|
46
55
|
const answers = {};
|
|
@@ -140,7 +149,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
140
149
|
kind: 'failed',
|
|
141
150
|
stage: 'decide',
|
|
142
151
|
gatedCallId,
|
|
143
|
-
reason: `decide failed: ${String(e)}`,
|
|
152
|
+
reason: `decide failed: ${String(e)}${decideFailureNote(e)}`,
|
|
144
153
|
...(code !== undefined ? { code } : {}),
|
|
145
154
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
146
155
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
@@ -3,6 +3,8 @@ import { hostLog } from '../host.js';
|
|
|
3
3
|
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
4
4
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
5
5
|
import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
6
|
+
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
7
|
+
import { decideFailureNote } from '../decideFailureNote.js';
|
|
6
8
|
import { isPlanReviewModeAfter } from './hitlBridge.js';
|
|
7
9
|
import { engineCapString } from '../engineCapsCache.js';
|
|
8
10
|
import { planReviewQuestionId, REOPEN_ID_TAIL } from './gateIdentity.js';
|
|
@@ -326,11 +328,17 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
326
328
|
`This ${decision} was not applied: the engine no longer has a pending plan review for this task — an earlier decision most likely already went through (or the review expired). ` +
|
|
327
329
|
'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
|
|
328
330
|
}
|
|
331
|
+
else if (typeof status === 'number' && corruptStoredRowWhere(e) !== undefined) {
|
|
332
|
+
const where = corruptStoredRowWhere(e);
|
|
333
|
+
hostLog('debug', `planReviewWire: ${decision} → ${String(status)} corrupt (stored resume context unreadable)`);
|
|
334
|
+
effect = 'not_applied';
|
|
335
|
+
outcome = `This ${decision} was not applied. ${corruptStoredRowContent(where)}`;
|
|
336
|
+
}
|
|
329
337
|
else if (typeof status === 'number') {
|
|
330
338
|
const msg = e instanceof Error ? e.message : String(e);
|
|
331
339
|
hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
|
|
332
340
|
effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
|
|
333
|
-
outcome = `The plan_review decision failed: HTTP ${status} ${msg}`.trim()
|
|
341
|
+
outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
|
|
334
342
|
}
|
|
335
343
|
else {
|
|
336
344
|
outcome = `The plan_review decision could not reach the engine: ${String(e)}`;
|
|
@@ -375,6 +383,11 @@ export function reopenPlanReviewCard(taskId, opts) {
|
|
|
375
383
|
if (typeof taskId !== 'string' || taskId.length === 0) {
|
|
376
384
|
return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
|
|
377
385
|
}
|
|
386
|
+
if (decideInFlight.has(taskId)) {
|
|
387
|
+
hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — a decision for this task is in flight (its answer is still being delivered)`);
|
|
388
|
+
const refused = { reopened: false, _sema_decisionInFlight: true };
|
|
389
|
+
return receiptMs === undefined ? refused : Promise.resolve(refused);
|
|
390
|
+
}
|
|
378
391
|
const sessionKey = opts?.sessionKey ?? DEFAULT_SESSION_KEY;
|
|
379
392
|
if (!hasQuestionOverlayFor(sessionKey)) {
|
|
380
393
|
return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
|
|
@@ -7,6 +7,7 @@ import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded
|
|
|
7
7
|
import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
|
|
8
8
|
import { isRuleStoreUnreadableKind } from '../gateVocabulary.js';
|
|
9
9
|
import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
|
|
10
|
+
import { decideFailureNote } from '../decideFailureNote.js';
|
|
10
11
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
11
12
|
import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, DENIAL_LIMIT_KINDS, } from '@sema-agent/sdk';
|
|
12
13
|
export function isFsApprovalGate(gate) {
|
|
@@ -199,7 +200,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
199
200
|
kind: 'failed',
|
|
200
201
|
stage: 'decide',
|
|
201
202
|
gatedCallId,
|
|
202
|
-
reason: `decide(approve) failed: ${String(e)}`,
|
|
203
|
+
reason: `decide(approve) failed: ${String(e)}${decideFailureNote(e)}`,
|
|
203
204
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
204
205
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
205
206
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
|
@@ -256,7 +257,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
256
257
|
kind: 'failed',
|
|
257
258
|
stage: 'decide',
|
|
258
259
|
gatedCallId,
|
|
259
|
-
reason: `decide(deny) failed: ${String(e)}`,
|
|
260
|
+
reason: `decide(deny) failed: ${String(e)}${decideFailureNote(e)}`,
|
|
260
261
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
261
262
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
262
263
|
...(wireCode !== undefined ? { errorCode: wireCode } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,9 @@ export * from './promptProfileWireCaps.js';
|
|
|
77
77
|
export * from './retainBackgroundWireCaps.js';
|
|
78
78
|
export * from './oneShotWireCaps.js';
|
|
79
79
|
export * from './rewindWireCaps.js';
|
|
80
|
+
export * from './rewindArchiveCapability.js';
|
|
81
|
+
export * from './fileHistoryCaptureCapability.js';
|
|
82
|
+
export * from './registryQuotaUsage.js';
|
|
80
83
|
export * from './selfOrchestrationWireCaps.js';
|
|
81
84
|
export * from './skillsWireCaps.js';
|
|
82
85
|
export * from './ultracodeWireCaps.js';
|
|
@@ -84,6 +87,7 @@ export * from './selfOrchestrationDenial.js';
|
|
|
84
87
|
export * from './webSearchWireCaps.js';
|
|
85
88
|
export * from './liveModelCatalog.js';
|
|
86
89
|
export * from './modelBudgetRule.js';
|
|
90
|
+
export * from './modelIdentityResolvability.js';
|
|
87
91
|
export * from './sessionModelLatch.js';
|
|
88
92
|
export * from './effortWire.js';
|
|
89
93
|
export * from './adapter/types.js';
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,9 @@ export * from './promptProfileWireCaps.js';
|
|
|
76
76
|
export * from './retainBackgroundWireCaps.js';
|
|
77
77
|
export * from './oneShotWireCaps.js';
|
|
78
78
|
export * from './rewindWireCaps.js';
|
|
79
|
+
export * from './rewindArchiveCapability.js';
|
|
80
|
+
export * from './fileHistoryCaptureCapability.js';
|
|
81
|
+
export * from './registryQuotaUsage.js';
|
|
79
82
|
export * from './selfOrchestrationWireCaps.js';
|
|
80
83
|
export * from './skillsWireCaps.js';
|
|
81
84
|
export * from './ultracodeWireCaps.js';
|
|
@@ -83,6 +86,7 @@ export * from './selfOrchestrationDenial.js';
|
|
|
83
86
|
export * from './webSearchWireCaps.js';
|
|
84
87
|
export * from './liveModelCatalog.js';
|
|
85
88
|
export * from './modelBudgetRule.js';
|
|
89
|
+
export * from './modelIdentityResolvability.js';
|
|
86
90
|
export * from './sessionModelLatch.js';
|
|
87
91
|
export * from './effortWire.js';
|
|
88
92
|
export * from './adapter/types.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type ModelIdentityLaneTuple = readonly [
|
|
2
|
+
'processEnv',
|
|
3
|
+
'settingsEnv',
|
|
4
|
+
'modelCatalogFile',
|
|
5
|
+
'followedSettingsFile',
|
|
6
|
+
'followedProviderEnv'
|
|
7
|
+
];
|
|
8
|
+
export type ModelIdentityLane = ModelIdentityLaneTuple[number];
|
|
9
|
+
export type ModelIdentityLaneReading = 'complete' | 'partial' | 'absent' | 'unreadable';
|
|
10
|
+
export type ModelIdentityLanes = {
|
|
11
|
+
[L in ModelIdentityLane]: L extends 'modelCatalogFile' ? Exclude<ModelIdentityLaneReading, 'complete'> : ModelIdentityLaneReading;
|
|
12
|
+
};
|
|
13
|
+
export type EngineAnswerObservation = 'answered' | 'not_observed' | 'unreachable';
|
|
14
|
+
export interface LocalEngineModelIdentityReading {
|
|
15
|
+
engine: 'local';
|
|
16
|
+
lanes: ModelIdentityLanes;
|
|
17
|
+
engineAnswer?: EngineAnswerObservation;
|
|
18
|
+
}
|
|
19
|
+
export interface RemoteEngineModelIdentityReading {
|
|
20
|
+
engine: 'remote';
|
|
21
|
+
engineAnswer: EngineAnswerObservation;
|
|
22
|
+
}
|
|
23
|
+
export type ModelIdentityReading = LocalEngineModelIdentityReading | RemoteEngineModelIdentityReading;
|
|
24
|
+
export type ModelIdentityResolvability = 'resolvable' | 'not_resolvable' | 'unknown';
|
|
25
|
+
export type ModelSetupDecision = 'offer_setup' | 'explain_unconfigured' | 'proceed';
|
|
26
|
+
export declare function modelIdentityResolvability(reading: ModelIdentityReading): ModelIdentityResolvability;
|
|
27
|
+
export declare function modelIdentityDetail(reading: ModelIdentityReading): string;
|
|
28
|
+
export declare function modelSetupDecision(identity: ModelIdentityResolvability, hostCanConfigureModel: boolean): ModelSetupDecision;
|
|
29
|
+
export declare function modelSetupNotice(decision: ModelSetupDecision): string | null;
|
|
30
|
+
export declare function observedEngineAnswer(baseUrl?: string | undefined): EngineAnswerObservation;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { engineCapValue } from './engineCapsCache.js';
|
|
2
|
+
import { engineWireTarget } from './engineWireTarget.js';
|
|
3
|
+
function laneOrder() {
|
|
4
|
+
return ['processEnv', 'settingsEnv', 'modelCatalogFile', 'followedSettingsFile', 'followedProviderEnv'];
|
|
5
|
+
}
|
|
6
|
+
function malformed() {
|
|
7
|
+
return { verdict: 'unknown', why: 'malformed' };
|
|
8
|
+
}
|
|
9
|
+
function own(o, key) {
|
|
10
|
+
if (!Object.hasOwn(o, key))
|
|
11
|
+
return undefined;
|
|
12
|
+
return { v: o[key] };
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(v) {
|
|
15
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
16
|
+
}
|
|
17
|
+
function laneState(lanes, lane) {
|
|
18
|
+
const hit = own(lanes, lane);
|
|
19
|
+
if (hit === undefined)
|
|
20
|
+
return 'not_reported';
|
|
21
|
+
const v = hit.v;
|
|
22
|
+
const _catalogPin = true;
|
|
23
|
+
void _catalogPin;
|
|
24
|
+
if (v === 'complete')
|
|
25
|
+
return lane === 'modelCatalogFile' ? 'unusable' : v;
|
|
26
|
+
return v === 'partial' || v === 'absent' || v === 'unreadable' ? v : 'unusable';
|
|
27
|
+
}
|
|
28
|
+
function analyse(reading) {
|
|
29
|
+
try {
|
|
30
|
+
if (!isPlainObject(reading))
|
|
31
|
+
return malformed();
|
|
32
|
+
const answer = own(reading, 'engineAnswer')?.v;
|
|
33
|
+
if (answer === 'answered')
|
|
34
|
+
return { verdict: 'resolvable', why: 'engine_answered' };
|
|
35
|
+
const engine = own(reading, 'engine')?.v;
|
|
36
|
+
if (engine === 'remote') {
|
|
37
|
+
return { verdict: 'unknown', why: 'remote', answer: answer === 'unreachable' ? 'unreachable' : 'not_observed' };
|
|
38
|
+
}
|
|
39
|
+
if (engine !== 'local')
|
|
40
|
+
return malformed();
|
|
41
|
+
const lanesHit = own(reading, 'lanes')?.v;
|
|
42
|
+
const order = laneOrder();
|
|
43
|
+
if (!isPlainObject(lanesHit))
|
|
44
|
+
return { verdict: 'unknown', why: 'lanes', unreadable: [], unreported: [...order], unusable: [] };
|
|
45
|
+
const partial = [];
|
|
46
|
+
const unreadable = [];
|
|
47
|
+
const unreported = [];
|
|
48
|
+
const unusable = [];
|
|
49
|
+
for (const lane of order) {
|
|
50
|
+
const s = laneState(lanesHit, lane);
|
|
51
|
+
if (s === 'complete')
|
|
52
|
+
return { verdict: 'resolvable', why: 'lane', lane };
|
|
53
|
+
if (s === 'partial')
|
|
54
|
+
partial.push(lane);
|
|
55
|
+
else if (s === 'unreadable')
|
|
56
|
+
unreadable.push(lane);
|
|
57
|
+
else if (s === 'not_reported')
|
|
58
|
+
unreported.push(lane);
|
|
59
|
+
else if (s === 'unusable')
|
|
60
|
+
unusable.push(lane);
|
|
61
|
+
}
|
|
62
|
+
if (unreadable.length > 0 || unreported.length > 0 || unusable.length > 0) {
|
|
63
|
+
return { verdict: 'unknown', why: 'lanes', unreadable, unreported, unusable };
|
|
64
|
+
}
|
|
65
|
+
return { verdict: 'not_resolvable', partial };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return malformed();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function modelIdentityResolvability(reading) {
|
|
72
|
+
return analyse(reading).verdict;
|
|
73
|
+
}
|
|
74
|
+
function laneLabel(lane) {
|
|
75
|
+
switch (lane) {
|
|
76
|
+
case 'processEnv':
|
|
77
|
+
return 'the environment this client was started with';
|
|
78
|
+
case 'settingsEnv':
|
|
79
|
+
return "this client's settings file";
|
|
80
|
+
case 'modelCatalogFile':
|
|
81
|
+
return 'the local model catalog file';
|
|
82
|
+
case 'followedSettingsFile':
|
|
83
|
+
return "another tool's settings file that this client follows";
|
|
84
|
+
case 'followedProviderEnv':
|
|
85
|
+
return 'the provider environment variables this client follows';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function joinPhrases(xs) {
|
|
89
|
+
if (xs.length <= 1)
|
|
90
|
+
return xs.join('');
|
|
91
|
+
return `${xs.slice(0, -1).join(', ')} and ${xs[xs.length - 1]}`;
|
|
92
|
+
}
|
|
93
|
+
export function modelIdentityDetail(reading) {
|
|
94
|
+
const a = analyse(reading);
|
|
95
|
+
switch (a.verdict) {
|
|
96
|
+
case 'resolvable':
|
|
97
|
+
if (a.why === 'engine_answered') {
|
|
98
|
+
return 'the engine is up and answering, and it does not start without a model name, so it has one — whether its gateway accepts that name is a separate check';
|
|
99
|
+
}
|
|
100
|
+
return `${laneLabel(a.lane)} supplies a model name, so the local engine can start with it — whether its gateway accepts that name is a separate check`;
|
|
101
|
+
case 'not_resolvable':
|
|
102
|
+
if (a.partial.length > 0) {
|
|
103
|
+
const labels = joinPhrases(a.partial.map(laneLabel));
|
|
104
|
+
return `no model name is configured for the local engine — ${labels} ${a.partial.length > 1 ? 'carry' : 'carries'} part of a model configuration (such as a gateway address or a credential) but no model name, and the engine does not start without one`;
|
|
105
|
+
}
|
|
106
|
+
return 'no model name is configured for the local engine — none of the places this client reads supplies one, and the engine does not start without it';
|
|
107
|
+
case 'unknown':
|
|
108
|
+
if (a.why === 'remote') {
|
|
109
|
+
return a.answer === 'unreachable'
|
|
110
|
+
? 'the engine runs elsewhere and did not answer — it may be unreachable or may have failed to start; its model configuration lives with that deployment, so whether it has a model name is unknown'
|
|
111
|
+
: 'the engine runs elsewhere and has not been seen answering yet — its model configuration lives with that deployment, so whether it has a model name is unknown';
|
|
112
|
+
}
|
|
113
|
+
if (a.why === 'lanes') {
|
|
114
|
+
const parts = [
|
|
115
|
+
...a.unreadable.map((l) => `${laneLabel(l)} could not be read`),
|
|
116
|
+
...a.unreported.map((l) => `${laneLabel(l)} was not reported`),
|
|
117
|
+
...a.unusable.map((l) => `${laneLabel(l)} gave no usable reading`),
|
|
118
|
+
];
|
|
119
|
+
return `whether the local engine has a model name is unknown — ${joinPhrases(parts)}, and no other place this client reads provides one`;
|
|
120
|
+
}
|
|
121
|
+
return "this reading of the engine's model configuration could not be interpreted, so whether the engine has a model name is unknown";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function modelSetupDecision(identity, hostCanConfigureModel) {
|
|
125
|
+
if (identity !== 'not_resolvable')
|
|
126
|
+
return 'proceed';
|
|
127
|
+
if (hostCanConfigureModel === true)
|
|
128
|
+
return 'offer_setup';
|
|
129
|
+
if (hostCanConfigureModel === false)
|
|
130
|
+
return 'explain_unconfigured';
|
|
131
|
+
return 'proceed';
|
|
132
|
+
}
|
|
133
|
+
export function modelSetupNotice(decision) {
|
|
134
|
+
switch (decision) {
|
|
135
|
+
case 'offer_setup':
|
|
136
|
+
return 'no model name is configured for the engine, and it does not start without one — opening model setup';
|
|
137
|
+
case 'explain_unconfigured':
|
|
138
|
+
return 'this engine has no model configured, and it cannot be configured from this client — ask whoever runs the engine to set one up';
|
|
139
|
+
default:
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export function observedEngineAnswer(baseUrl = engineWireTarget()?.baseUrl) {
|
|
144
|
+
return engineCapValue(baseUrl, 'version').kind === 'unobserved' ? 'not_observed' : 'answered';
|
|
145
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isCcToolDenialKind } from './gateVocabulary.js';
|
|
2
2
|
export { flattenWireOutput as flattenToolOutput } from './adapt/wireShapes.js';
|
|
3
3
|
import { flattenWireOutput } from './adapt/wireShapes.js';
|
|
4
|
+
import { parseModelFacingBash } from './toolResult.js';
|
|
5
|
+
import { toolResultEnvelopeParts, toolResultOutcomeOf } from './adapt/toolCards.js';
|
|
4
6
|
function bashExitCodeFailed(toolName, text, structured) {
|
|
5
7
|
if (typeof toolName !== 'string' || toolName.toLowerCase() !== 'bash')
|
|
6
8
|
return false;
|
|
@@ -10,6 +12,8 @@ function bashExitCodeFailed(toolName, text, structured) {
|
|
|
10
12
|
const ec = st?.exitCode;
|
|
11
13
|
if (typeof ec === 'number')
|
|
12
14
|
return ec !== 0;
|
|
15
|
+
if (parseModelFacingBash(text)?.note !== undefined)
|
|
16
|
+
return false;
|
|
13
17
|
const m = /^exit code: (\d+)\b/.exec(text);
|
|
14
18
|
return m !== null && m[1] !== '0';
|
|
15
19
|
}
|
|
@@ -17,6 +21,14 @@ export function toolEndResultToUserFrame(arm) {
|
|
|
17
21
|
if (typeof arm.toolCallId !== 'string' || arm.toolCallId.length === 0)
|
|
18
22
|
return null;
|
|
19
23
|
const text = flattenWireOutput(arm.output);
|
|
24
|
+
const parent = typeof arm.parentToolCallId === 'string' ? arm.parentToolCallId : null;
|
|
25
|
+
const outcome = toolResultOutcomeOf(typeof arm.toolName === 'string' ? arm.toolName : '', {
|
|
26
|
+
output: arm.output,
|
|
27
|
+
structured: arm.structured,
|
|
28
|
+
truncated: arm.truncated === true,
|
|
29
|
+
isError: arm.isError === true,
|
|
30
|
+
}, undefined, parent === null ? 'leader' : 'subagent');
|
|
31
|
+
const envelope = toolResultEnvelopeParts(outcome);
|
|
20
32
|
return {
|
|
21
33
|
type: 'user',
|
|
22
34
|
message: {
|
|
@@ -30,8 +42,10 @@ export function toolEndResultToUserFrame(arm) {
|
|
|
30
42
|
},
|
|
31
43
|
],
|
|
32
44
|
},
|
|
33
|
-
parent_tool_use_id:
|
|
45
|
+
parent_tool_use_id: parent,
|
|
34
46
|
...(isCcToolDenialKind(arm._sema_denial_kind) ? { _sema_denial_kind: arm._sema_denial_kind } : {}),
|
|
47
|
+
...(Object.hasOwn(envelope, 'tool_use_result') ? { tool_use_result: envelope.tool_use_result } : {}),
|
|
48
|
+
...(Object.hasOwn(envelope, '_sema_degraded') ? { _sema_degraded: envelope._sema_degraded } : {}),
|
|
35
49
|
uuid: arm.uuid,
|
|
36
50
|
session_id: arm.session_id,
|
|
37
51
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type QuotaAmount = {
|
|
2
|
+
kind: 'value';
|
|
3
|
+
value: number;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'unlimited';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'unknown';
|
|
8
|
+
};
|
|
9
|
+
export type QuotaResetAt = {
|
|
10
|
+
kind: 'at';
|
|
11
|
+
iso: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'not-exhausted';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'unknown';
|
|
16
|
+
};
|
|
17
|
+
export interface QuotaWindowReading {
|
|
18
|
+
readonly window: 'fiveHour' | 'weekly';
|
|
19
|
+
readonly limit: QuotaAmount;
|
|
20
|
+
readonly used: QuotaAmount;
|
|
21
|
+
readonly remaining: QuotaAmount;
|
|
22
|
+
readonly exhausted: boolean | undefined;
|
|
23
|
+
readonly resetAt: QuotaResetAt;
|
|
24
|
+
readonly isEstimate: boolean | undefined;
|
|
25
|
+
}
|
|
26
|
+
export type QuotaConfigured = 'configured' | 'not-configured' | 'unknown';
|
|
27
|
+
export interface QuotaUsageReading {
|
|
28
|
+
readonly principal: string | undefined;
|
|
29
|
+
readonly configured: QuotaConfigured;
|
|
30
|
+
readonly source: string | undefined;
|
|
31
|
+
readonly fiveHour: QuotaWindowReading;
|
|
32
|
+
readonly weekly: QuotaWindowReading;
|
|
33
|
+
readonly lastResetAt: {
|
|
34
|
+
kind: 'at';
|
|
35
|
+
iso: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'never';
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'unknown';
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function projectRegistryQuotaUsage(body: unknown): QuotaUsageReading | undefined;
|
|
43
|
+
export declare function quotaAmountText(amount: QuotaAmount): string;
|
|
44
|
+
export declare function registryQuotaDoctorDetail(reading: QuotaUsageReading | undefined): string;
|