@sema-agent/client-core 0.89.0 → 0.91.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 +297 -0
- package/README.md +35 -24
- package/dist/adapt/arms.js +9 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
- package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
- package/dist/adapter/downstream/wiringManifestView.js +16 -0
- package/dist/adapter/runStream.d.ts +2 -0
- package/dist/adapter/runStream.js +180 -38
- package/dist/askSurvivesStandingAnswer.d.ts +10 -0
- package/dist/askSurvivesStandingAnswer.js +40 -0
- package/dist/attachmentSeatCapability.d.ts +11 -0
- package/dist/attachmentSeatCapability.js +27 -0
- package/dist/attachmentsWireCaps.d.ts +22 -2
- package/dist/attachmentsWireCaps.js +95 -22
- package/dist/compensations.js +10 -14
- package/dist/controlRouter.d.ts +9 -2
- package/dist/controlRouter.js +66 -28
- package/dist/coreValuePorts.d.ts +1 -0
- package/dist/decideFailureNote.js +4 -2
- package/dist/disableSkillsWire.d.ts +1 -0
- package/dist/disableSkillsWire.js +8 -1
- package/dist/displayUntrusted.js +153 -4
- package/dist/engineCapReader.js +12 -1
- package/dist/engineCapsCache.d.ts +3 -1
- package/dist/engineCapsCache.js +56 -6
- package/dist/engineClosedSets.d.ts +2 -0
- package/dist/engineClosedSets.js +3 -0
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +10 -0
- package/dist/engineNoticeCodes.d.ts +2 -9
- package/dist/engineNoticeCodes.js +19 -27
- package/dist/fileHistoryLegNotice.d.ts +20 -0
- package/dist/fileHistoryLegNotice.js +122 -0
- package/dist/gateOutcome.d.ts +1 -1
- package/dist/gateOutcome.js +1 -0
- package/dist/generated/engineFactTables.d.ts +13 -0
- package/dist/generated/engineFactTables.js +50 -0
- package/dist/generated/engineNoticeTables.js +0 -3
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- package/dist/hitl/crashConverged.d.ts +7 -0
- package/dist/hitl/crashConverged.js +57 -0
- package/dist/hitl/crashConvergedNotice.d.ts +4 -0
- package/dist/hitl/crashConvergedNotice.js +57 -0
- package/dist/hitl/hitlBridge.d.ts +7 -4
- package/dist/hitl/parkResolver.d.ts +7 -2
- package/dist/hitl/parkResolver.js +19 -7
- package/dist/hitl/planReviewWire.d.ts +1 -1
- package/dist/hitl/planReviewWire.js +18 -4
- package/dist/hitl/sessionPolicyWire.d.ts +5 -0
- package/dist/hitl/sessionPolicyWire.js +63 -0
- package/dist/hitl/suspendedReopen.js +2 -2
- package/dist/hitl/toolApprovalWire.d.ts +4 -1
- package/dist/hitl/toolApprovalWire.js +20 -0
- package/dist/hookMatcherVerdict.d.ts +29 -0
- package/dist/hookMatcherVerdict.js +265 -0
- package/dist/hookToolAliases.d.ts +3 -0
- package/dist/hookToolAliases.js +10 -0
- package/dist/humanTurn.d.ts +91 -0
- package/dist/humanTurn.js +253 -0
- package/dist/humanTurnRead.d.ts +105 -0
- package/dist/humanTurnRead.js +331 -0
- package/dist/index.d.ts +30 -5
- package/dist/index.js +19 -4
- package/dist/inheritEnvWire.js +1 -1
- package/dist/liveModelCatalog.d.ts +2 -0
- package/dist/liveModelCatalog.js +51 -3
- package/dist/permissionRuleIssue.d.ts +1 -1
- package/dist/permissionRuleIssue.js +22 -3
- package/dist/permissionsAllowGrantCapability.d.ts +18 -0
- package/dist/permissionsAllowGrantCapability.js +84 -0
- package/dist/permissionsDeclaration.d.ts +48 -0
- package/dist/permissionsDeclaration.js +307 -0
- package/dist/promptDelivery.d.ts +16 -5
- package/dist/promptDelivery.js +172 -32
- package/dist/providerRejection.d.ts +3 -1
- package/dist/providerRejection.js +27 -1
- package/dist/readDenyTiers.d.ts +10 -0
- package/dist/readDenyTiers.js +29 -0
- package/dist/request/taskRequest.js +27 -1
- package/dist/resumeRefusalCopy.d.ts +11 -0
- package/dist/resumeRefusalCopy.js +61 -4
- package/dist/rewindArchiveCapability.js +4 -4
- package/dist/rewindWireCaps.d.ts +2 -1
- package/dist/rewindWireCaps.js +19 -2
- package/dist/sdkRegistryTransit.d.ts +1 -0
- package/dist/sdkRegistryTransit.js +1 -0
- package/dist/seam.d.ts +2 -0
- package/dist/supportFloor.d.ts +10 -0
- package/dist/supportFloor.js +42 -0
- package/dist/taskRequestWords.d.ts +7 -0
- package/dist/taskRequestWords.js +24 -0
- package/dist/toolHistoryBadTurn.d.ts +1 -0
- package/dist/toolHistoryBadTurn.js +89 -0
- package/dist/toolHistoryMismatch.d.ts +2 -1
- package/dist/toolHistoryMismatch.js +19 -6
- package/dist/userAttachmentEnvelope.d.ts +61 -0
- package/dist/userAttachmentEnvelope.js +204 -0
- package/dist/wireFailureShape.d.ts +1 -0
- package/dist/wireFailureShape.js +4 -0
- package/dist/wireRefusalCopy.d.ts +6 -0
- package/dist/wireRefusalCopy.js +26 -3
- package/docs/INTEGRATION-CLIENTS.md +1369 -18
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readRunTerminal } from './runTerminal.js';
|
|
2
|
+
import { classifyToolHistoryMismatch } from './toolHistoryMismatch.js';
|
|
2
3
|
const PROVIDER_STATUS_TIERS = Object.freeze([
|
|
3
4
|
Object.freeze([400, 400, 'invalid_request']),
|
|
4
5
|
Object.freeze([401, 401, 'authentication_failed']),
|
|
@@ -24,8 +25,31 @@ const NOT_BY_EDITING_TAIL = new Map([
|
|
|
24
25
|
['server_error', ': the provider reported an internal error — try again later.'],
|
|
25
26
|
[UNTIERED, ' for a reason that editing it will not fix — check the provider account or try again later.'],
|
|
26
27
|
]);
|
|
28
|
+
function toolHistoryMismatchCarrier(carrier) {
|
|
29
|
+
return carrier !== undefined && carrier !== null && classifyToolHistoryMismatch(carrier) !== null;
|
|
30
|
+
}
|
|
27
31
|
const PROVIDER_REJECTED = 'provider_rejected';
|
|
28
|
-
|
|
32
|
+
const RESEND_TEXT_STATUS = /\(HTTP (\d{3})\)/;
|
|
33
|
+
export function readProviderRejectionResendKey(v) {
|
|
34
|
+
try {
|
|
35
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
36
|
+
return undefined;
|
|
37
|
+
const got = v;
|
|
38
|
+
const resend = got.resend;
|
|
39
|
+
const text = got.text;
|
|
40
|
+
if (typeof text !== 'string' || (resend !== 'edit_and_resend' && resend !== 'not_by_editing'))
|
|
41
|
+
return undefined;
|
|
42
|
+
const hit = RESEND_TEXT_STATUS.exec(text);
|
|
43
|
+
if (hit === null || hit[1] === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
const again = providerRejectionResend({ kind: PROVIDER_REJECTED, status: Number(hit[1]) });
|
|
46
|
+
return again !== undefined && again.resend === resend && again.text === text ? { resend: again.resend, text: again.text } : undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function providerRejectionResend(detail, carrier) {
|
|
29
53
|
let read;
|
|
30
54
|
try {
|
|
31
55
|
const r = readRunTerminal({ terminal: { kind: 'failed', detail } });
|
|
@@ -39,6 +63,8 @@ export function providerRejectionResend(detail) {
|
|
|
39
63
|
const status = Object.hasOwn(read, 'status') ? read.status : undefined;
|
|
40
64
|
if (status === undefined)
|
|
41
65
|
return undefined;
|
|
66
|
+
if (toolHistoryMismatchCarrier(carrier))
|
|
67
|
+
return undefined;
|
|
42
68
|
const tier = providerStatusTier(status);
|
|
43
69
|
return tier === EDITABLE_TIER
|
|
44
70
|
? {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const READ_DENY_BUILTIN_TIERS: readonly ["credentials", "shell-history", "browser", "wallet", "agent-config"];
|
|
2
|
+
export declare const READ_DENY_DEFAULT_TIERS: readonly ["credentials", "agent-config"];
|
|
3
|
+
export type ReadDenyBuiltinTiersParse = {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly value: string | null;
|
|
6
|
+
} | {
|
|
7
|
+
readonly ok: false;
|
|
8
|
+
readonly why: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function parseReadDenyBuiltinTiers(input: string): ReadDenyBuiltinTiersParse;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { displayUntrusted } from './displayUntrusted.js';
|
|
2
|
+
import { CORE_READ_DENY_BUILTIN_TIERS, CORE_READ_DENY_DEFAULT_TIERS } from './generated/engineFactTables.js';
|
|
3
|
+
export const READ_DENY_BUILTIN_TIERS = CORE_READ_DENY_BUILTIN_TIERS;
|
|
4
|
+
export const READ_DENY_DEFAULT_TIERS = CORE_READ_DENY_DEFAULT_TIERS;
|
|
5
|
+
const NONE_WORD = 'none';
|
|
6
|
+
const SHOWN_ITEM_MAX = 40;
|
|
7
|
+
const SHOWN_ITEM_WINDOW = 2000;
|
|
8
|
+
const refuse = (why) => ({ ok: false, why });
|
|
9
|
+
const shown = (item) => displayUntrusted(item.length > SHOWN_ITEM_WINDOW ? item.slice(0, SHOWN_ITEM_WINDOW) : item, { max: SHOWN_ITEM_MAX });
|
|
10
|
+
export function parseReadDenyBuiltinTiers(input) {
|
|
11
|
+
if (typeof input !== 'string')
|
|
12
|
+
return refuse(`expected a string: comma-separated tier names, or the single word "${NONE_WORD}"`);
|
|
13
|
+
const text = input.trim();
|
|
14
|
+
if (text === '')
|
|
15
|
+
return { ok: true, value: null };
|
|
16
|
+
const parts = text.split(',').map((p) => p.trim());
|
|
17
|
+
if (parts.some((p) => p === ''))
|
|
18
|
+
return refuse('an empty entry between commas');
|
|
19
|
+
if (parts.includes(NONE_WORD)) {
|
|
20
|
+
return parts.length === 1 ? { ok: true, value: NONE_WORD } : refuse(`"${NONE_WORD}" must be the only entry`);
|
|
21
|
+
}
|
|
22
|
+
const known = READ_DENY_BUILTIN_TIERS;
|
|
23
|
+
const unknown = parts.filter((p) => !known.includes(p));
|
|
24
|
+
if (unknown.length > 0) {
|
|
25
|
+
const more = unknown.length > 1 ? ` (and ${unknown.length - 1} more)` : '';
|
|
26
|
+
return refuse(`unknown tier "${shown(unknown[0])}"${more}; known tiers: ${known.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, value: parts.join(',') };
|
|
29
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { refuseBadDisableSkills, refuseDisableSkillsConflict } from '../disableSkillsWire.js';
|
|
2
|
+
import { ATTACHMENTS_SPEC_KEYS, AttachmentsSpecRefused } from '../attachmentsWireCaps.js';
|
|
3
|
+
import { retiredAttachmentsKeyOf } from '../taskRequestWords.js';
|
|
2
4
|
export const REQUEST_FIELD_MATRIX = [
|
|
3
5
|
{ field: 'objective', lanes: ['interactive', 'print', 'utility'], live: false, why: '本 turn 的真实输入,每条车道都必需', whyUtilityLane: '恒座:三条车道都必须带上本次提交的模型面输入' },
|
|
4
6
|
{ field: 'sessionId', lanes: ['interactive', 'print', 'utility'], live: false, why: '单命名空间(TP-A Step 2):壳 id 即引擎 session id;print 在非 live 下保留 mock 常量', whyUtilityLane: '恒座:side-channel 提交走**独立**会话 id(壳另铸一个,绝不复用用户会话那一个 —— 复用会把这次提交的转录混进用户会话)' },
|
|
@@ -451,6 +453,29 @@ const refuseBadMemoryOff = (v) => {
|
|
|
451
453
|
'`"on"`,`{ enabled: false }` 对象形也不是 wire 拼法)且对坏拼写**响亮拒**(server 400 `request.field_invalid`)。' +
|
|
452
454
|
'本层若静默删键,这一次运行会照常挂上记忆面而声明人不会知道 —— 与 memoryCapture 同一条纪律。错误文本只报形状,不回显值。');
|
|
453
455
|
};
|
|
456
|
+
const withoutRetiredAttachmentsKeys = (v) => {
|
|
457
|
+
if (typeof v !== 'object' || v === null)
|
|
458
|
+
return v;
|
|
459
|
+
let drop;
|
|
460
|
+
for (const key of Object.keys(v)) {
|
|
461
|
+
const why = retiredAttachmentsKeyOf(key, 'key')?.why;
|
|
462
|
+
if (why === undefined)
|
|
463
|
+
continue;
|
|
464
|
+
const d = Object.getOwnPropertyDescriptor(v, key);
|
|
465
|
+
if (d !== undefined && Object.prototype.hasOwnProperty.call(d, 'value') && d.value === undefined) {
|
|
466
|
+
;
|
|
467
|
+
(drop ??= []).push(key);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
throw new AttachmentsSpecRefused(`buildTaskRequest: refusing attachments key ${JSON.stringify(key)} — ${why}`, { token: key, why, allowed: ATTACHMENTS_SPEC_KEYS, key });
|
|
471
|
+
}
|
|
472
|
+
if (drop === undefined)
|
|
473
|
+
return v;
|
|
474
|
+
const copy = { ...v };
|
|
475
|
+
for (const key of drop)
|
|
476
|
+
delete copy[key];
|
|
477
|
+
return copy;
|
|
478
|
+
};
|
|
454
479
|
const refuseBadExcludeAllTools = (v) => {
|
|
455
480
|
if (v === undefined || v === null || v === true || v === false)
|
|
456
481
|
return;
|
|
@@ -784,6 +809,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
784
809
|
...(onSetting('hooks') ? { hooks: s.hooks } : {}),
|
|
785
810
|
...(s.outputStyle !== undefined ? { outputStyle: s.outputStyle } : {}),
|
|
786
811
|
};
|
|
812
|
+
const attachmentsForWire = on('attachments') ? withoutRetiredAttachmentsKeys(i.attachments) : undefined;
|
|
787
813
|
const out = {
|
|
788
814
|
objective: i.objective,
|
|
789
815
|
sessionId: i.sessionId,
|
|
@@ -802,7 +828,7 @@ export function assembleTaskRequest(input, lane) {
|
|
|
802
828
|
...(on('retainBackgroundProcesses') ? { retainBackgroundProcesses: i.retainBackgroundProcesses } : {}),
|
|
803
829
|
...(on('promptProfile') ? { promptProfile: i.promptProfile } : {}),
|
|
804
830
|
...(on('enableFork') ? { enableFork: i.enableFork } : {}),
|
|
805
|
-
...(on('attachments') ? { attachments:
|
|
831
|
+
...(on('attachments') ? { attachments: attachmentsForWire } : {}),
|
|
806
832
|
...(on('repo') ? { repo: i.repo } : {}),
|
|
807
833
|
...(on('council') ? { council: true } : {}),
|
|
808
834
|
...(on('debate') ? { debate: true } : {}),
|
|
@@ -46,3 +46,14 @@ export interface ParkedResumeStartupDetail {
|
|
|
46
46
|
}
|
|
47
47
|
export declare function parkedResumeStartupFromError(err: unknown, decision?: ParkedResumeStartupDecision): ParkedResumeStartupDetail | null;
|
|
48
48
|
export declare function parkedResumeStartupContent(detail: ParkedResumeStartupDetail): string;
|
|
49
|
+
export interface CheckpointUnsupportedVersionDetail {
|
|
50
|
+
code: 'checkpoint.unsupported_version';
|
|
51
|
+
retriable: true;
|
|
52
|
+
}
|
|
53
|
+
export declare function checkpointUnsupportedVersionFromError(err: unknown): CheckpointUnsupportedVersionDetail | null;
|
|
54
|
+
export type CheckpointUnsupportedParkKind = 'tool_approval' | 'plan_review' | 'resource_park' | 'wake' | 'workflow_host';
|
|
55
|
+
export interface CheckpointUnsupportedVersionContext {
|
|
56
|
+
readonly parkKind?: CheckpointUnsupportedParkKind;
|
|
57
|
+
readonly runId?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function checkpointUnsupportedVersionContent(detail: CheckpointUnsupportedVersionDetail, ctx?: CheckpointUnsupportedVersionContext): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readErrorNumber, readErrorString } from './wireFailureShape.js';
|
|
2
|
-
import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, RESUME_SESSION_NOT_FOUND, } from './engineErrorCodes.js';
|
|
2
|
+
import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, RESUME_SESSION_NOT_FOUND, REQUEST_FIELD_CONFLICT, CHECKPOINT_UNSUPPORTED_VERSION, refusalSaysStillPending, } from './engineErrorCodes.js';
|
|
3
3
|
import { capForDisplay } from './fleetTaskDesc.js';
|
|
4
4
|
import { resumeRetryLaterFromError } from './wireErrorTriage.js';
|
|
5
5
|
export const RESUME_REFUSAL_CODES = Object.freeze([
|
|
@@ -88,8 +88,8 @@ export function resumePolicyBlockContent(detail) {
|
|
|
88
88
|
export { resumeReopenFromError, resumeReopenContent } from './hitl/suspendedReopen.js';
|
|
89
89
|
const RESUME_SESSION_NOT_FOUND_TAIL = `(${RESUME_SESSION_NOT_FOUND})`;
|
|
90
90
|
const REQUIRE_EXISTING_BY_STATUS = new Map([
|
|
91
|
-
[404, Object.freeze({ form: 'session_not_found', coarse: 'not_found.resource' })],
|
|
92
|
-
[422, Object.freeze({ form: 'session_id_missing', coarse: 'request.unprocessable' })],
|
|
91
|
+
[404, Object.freeze({ form: 'session_not_found', code: RESUME_SESSION_NOT_FOUND, coarse: 'not_found.resource' })],
|
|
92
|
+
[422, Object.freeze({ form: 'session_id_missing', code: REQUEST_FIELD_CONFLICT, coarse: 'request.unprocessable' })],
|
|
93
93
|
]);
|
|
94
94
|
export function requireExistingSessionRefusalFromError(err) {
|
|
95
95
|
if (typeof err !== 'object' || err === null)
|
|
@@ -101,7 +101,7 @@ export function requireExistingSessionRefusalFromError(err) {
|
|
|
101
101
|
if (byStatus === undefined)
|
|
102
102
|
return null;
|
|
103
103
|
const code = o['errorCode'];
|
|
104
|
-
if (code ===
|
|
104
|
+
if (code === byStatus.code)
|
|
105
105
|
return Object.freeze({ code: RESUME_SESSION_NOT_FOUND, form: byStatus.form });
|
|
106
106
|
if (code !== byStatus.coarse)
|
|
107
107
|
return null;
|
|
@@ -196,3 +196,60 @@ const PARKED_RESUME_STARTUP_SENTENCES = Object.freeze({
|
|
|
196
196
|
export function parkedResumeStartupContent(detail) {
|
|
197
197
|
return detail.cause === 'resume_rejected' ? PARKED_RESUME_STARTUP_SENTENCES.resume_rejected : PARKED_RESUME_STARTUP_SENTENCES.undetermined;
|
|
198
198
|
}
|
|
199
|
+
export function checkpointUnsupportedVersionFromError(err) {
|
|
200
|
+
if (typeof err !== 'object' || err === null)
|
|
201
|
+
return null;
|
|
202
|
+
let code;
|
|
203
|
+
try {
|
|
204
|
+
code = err.errorCode;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
if (code !== CHECKPOINT_UNSUPPORTED_VERSION || !refusalSaysStillPending(err))
|
|
210
|
+
return null;
|
|
211
|
+
return { code: CHECKPOINT_UNSUPPORTED_VERSION, retriable: true };
|
|
212
|
+
}
|
|
213
|
+
function checkpointUnsupportedHasRun(kind) {
|
|
214
|
+
switch (kind) {
|
|
215
|
+
case 'tool_approval':
|
|
216
|
+
case 'plan_review':
|
|
217
|
+
case 'resource_park':
|
|
218
|
+
return true;
|
|
219
|
+
case 'wake':
|
|
220
|
+
case 'workflow_host':
|
|
221
|
+
return false;
|
|
222
|
+
default:
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const CHECKPOINT_UNSUPPORTED_WHAT = 'This server cannot continue this parked work in the form it was stored in';
|
|
227
|
+
const CHECKPOINT_UNSUPPORTED_STILL_WAITING = 'Nothing was decided or resumed and the parked work is still waiting, but sending the same request again will not by itself continue it';
|
|
228
|
+
const CHECKPOINT_UNSUPPORTED_DRAIN = " — whoever operates this deployment can find such parked work before an upgrade with the server's pre-upgrade drain check";
|
|
229
|
+
export function checkpointUnsupportedVersionContent(detail, ctx) {
|
|
230
|
+
void detail;
|
|
231
|
+
let kind;
|
|
232
|
+
let runId;
|
|
233
|
+
try {
|
|
234
|
+
kind = ctx?.parkKind;
|
|
235
|
+
runId = ctx?.runId;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
kind = undefined;
|
|
239
|
+
runId = undefined;
|
|
240
|
+
}
|
|
241
|
+
const hasRun = checkpointUnsupportedHasRun(kind);
|
|
242
|
+
let outlet;
|
|
243
|
+
if (hasRun === true) {
|
|
244
|
+
const named = typeof runId === 'string' && runId.length > 0 ? ` (${capForDisplay(runId, 80)})` : '';
|
|
245
|
+
outlet = `If it was parked before this server was upgraded, cancel its run${named} first, then submit the work again as a new task`;
|
|
246
|
+
}
|
|
247
|
+
else if (hasRun === false) {
|
|
248
|
+
outlet = 'If it was parked before this server was upgraded, there is no run to cancel: start a new task in that conversation';
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
outlet =
|
|
252
|
+
'If it was parked before this server was upgraded, submit the work again as a new task: when it still has a run waiting on it (a tool approval, a plan review or a paused resource), cancel that run first; when it was waiting to be woken up, or was a workflow step continuing its host conversation, there is no run to cancel, so start a new task in that conversation';
|
|
253
|
+
}
|
|
254
|
+
return [CHECKPOINT_UNSUPPORTED_WHAT, CHECKPOINT_UNSUPPORTED_STILL_WAITING, outlet + CHECKPOINT_UNSUPPORTED_DRAIN].join(' · ');
|
|
255
|
+
}
|
|
@@ -109,17 +109,17 @@ export function rewindConversationDoctorDetail(reading) {
|
|
|
109
109
|
}
|
|
110
110
|
export function rewindRestoreFilesEpochDetail(reading) {
|
|
111
111
|
if (reading.kind !== 'present') {
|
|
112
|
-
return 'restore-files request
|
|
112
|
+
return 'restore-files request key not observed — this process has no usable capabilities reading for this engine, so a rewind with code restore still sends the key';
|
|
113
113
|
}
|
|
114
114
|
switch (reading.restoreFilesEpoch) {
|
|
115
115
|
case 'on':
|
|
116
116
|
return 'this engine announces the restore-files request key (tracked-set restore semantics)';
|
|
117
117
|
case 'off':
|
|
118
|
-
return 'this engine does not announce the restore-files request key
|
|
118
|
+
return 'this engine does not announce the restore-files request key, so a rewind with code restore built from this reading leaves the key out and moves the conversation only';
|
|
119
119
|
case 'not_reported':
|
|
120
|
-
return 'this engine does not report the restore-files epoch bit (older engine)
|
|
120
|
+
return 'this engine does not report the restore-files epoch bit (older engine), so a rewind with code restore built from this reading leaves the key out and moves the conversation only';
|
|
121
121
|
case 'malformed':
|
|
122
|
-
return 'the restore-files epoch bit is reported in an unreadable shape
|
|
122
|
+
return 'the restore-files epoch bit is reported in an unreadable shape, so a rewind with code restore built from this reading leaves the key out and moves the conversation only';
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
export function forgetRewindArchiveReading(baseUrl) {
|
package/dist/rewindWireCaps.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RewindArchiveReading } from './rewindArchiveCapability.js';
|
|
1
2
|
export interface SeamRewindSpec {
|
|
2
3
|
resumeAt?: string;
|
|
3
4
|
resumeAtMode?: 'at' | 'before';
|
|
@@ -5,5 +6,5 @@ export interface SeamRewindSpec {
|
|
|
5
6
|
rewindFilesTo?: string;
|
|
6
7
|
}
|
|
7
8
|
export type RewindMode = 'both' | 'conversation' | 'code';
|
|
8
|
-
export declare function rewindSpecForMode(mode: RewindMode, targetEntryId: string): SeamRewindSpec;
|
|
9
|
+
export declare function rewindSpecForMode(mode: RewindMode, targetEntryId: string, reading?: RewindArchiveReading): SeamRewindSpec;
|
|
9
10
|
export declare function projectRewind(pending: SeamRewindSpec | undefined): SeamRewindSpec;
|
package/dist/rewindWireCaps.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
function restoreFilesKeyMaySend(reading) {
|
|
2
|
+
if (reading === undefined)
|
|
3
|
+
return true;
|
|
4
|
+
try {
|
|
5
|
+
if (reading === null || typeof reading !== 'object')
|
|
6
|
+
return false;
|
|
7
|
+
const kind = reading.kind;
|
|
8
|
+
if (kind === 'unobserved')
|
|
9
|
+
return true;
|
|
10
|
+
return kind === 'present' && reading.restoreFilesEpoch === 'on';
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function rewindSpecForMode(mode, targetEntryId, reading) {
|
|
2
17
|
switch (mode) {
|
|
3
18
|
case 'both':
|
|
4
|
-
return
|
|
19
|
+
return restoreFilesKeyMaySend(reading)
|
|
20
|
+
? { resumeAt: targetEntryId, resumeAtMode: 'before', restoreFiles: true }
|
|
21
|
+
: { resumeAt: targetEntryId, resumeAtMode: 'before' };
|
|
5
22
|
case 'conversation':
|
|
6
23
|
return { resumeAt: targetEntryId, resumeAtMode: 'before' };
|
|
7
24
|
case 'code':
|
|
@@ -2,5 +2,6 @@ export { RegistryClient } from '@sema-agent/sdk/registry';
|
|
|
2
2
|
export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
|
|
3
3
|
export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
|
|
4
4
|
export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
|
|
5
|
+
export { requestDeviceCode, pollUntilApproved, refreshTokens, listScopes, switchScope } from '@sema-agent/sdk/registry';
|
|
5
6
|
export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
|
|
6
7
|
export type { RegistryTokens, TokenProvider, } from '@sema-agent/sdk/registry';
|
|
@@ -2,4 +2,5 @@ export { RegistryClient } from '@sema-agent/sdk/registry';
|
|
|
2
2
|
export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
|
|
3
3
|
export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
|
|
4
4
|
export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
|
|
5
|
+
export { requestDeviceCode, pollUntilApproved, refreshTokens, listScopes, switchScope } from '@sema-agent/sdk/registry';
|
|
5
6
|
export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
|
package/dist/seam.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage
|
|
|
3
3
|
import type { RunCostReconcile } from './adapter/downstream/terminalToSdkResult.js';
|
|
4
4
|
import type { WiringManifestAutoMode, WiringManifestModelGate, WiringManifestMcpEntryView, WiringManifestHookEntryView, WiringManifestLspSeamView, WiringManifestWriteProtectionView, WiringManifestAutoConsolidationView, WiringManifestReadDenyView, ContextUsageSectionView, PromptManifestSectionView, PromptManifestBlockView, PromptManifestToolView } from './adapter/downstream/eventToSdkMessage.js';
|
|
5
5
|
import type { ToolRosterView, ToolRosterDeltaView, WiringManifestHandsView } from './toolRoster.js';
|
|
6
|
+
import type { WiringManifestFleetView } from './adapter/downstream/wiringManifestView.js';
|
|
6
7
|
import type { SuspendedReopenReading } from './hitl/suspendedReopen.js';
|
|
7
8
|
export interface AbortSignalLike {
|
|
8
9
|
readonly aborted: boolean;
|
|
@@ -177,6 +178,7 @@ export interface WiringManifestChromeEvent {
|
|
|
177
178
|
instructions?: {
|
|
178
179
|
readonly selected: readonly string[];
|
|
179
180
|
};
|
|
181
|
+
fleet?: WiringManifestFleetView;
|
|
180
182
|
eventId?: string;
|
|
181
183
|
}
|
|
182
184
|
export interface TextSegmentCommittedRow {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type SupportFloorComponent = 'server' | 'core' | 'sdk';
|
|
2
|
+
export interface SupportFloor {
|
|
3
|
+
readonly server: string;
|
|
4
|
+
readonly core: string;
|
|
5
|
+
readonly sdk: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const SUPPORT_FLOOR: Readonly<SupportFloor>;
|
|
8
|
+
export declare function supportFloorOf(component: unknown): string | undefined;
|
|
9
|
+
export type EngineVersionTier = 'engine' | 'legacy' | 'unknown';
|
|
10
|
+
export declare function engineVersionTier(version: unknown, floor?: string): EngineVersionTier;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const SUPPORT_FLOOR = Object.freeze({
|
|
2
|
+
server: '7.104.0',
|
|
3
|
+
core: '7.33.8',
|
|
4
|
+
sdk: '12.0.1',
|
|
5
|
+
});
|
|
6
|
+
export function supportFloorOf(component) {
|
|
7
|
+
if (component === 'server' || component === 'core' || component === 'sdk')
|
|
8
|
+
return SUPPORT_FLOOR[component];
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
function releaseTriple(v) {
|
|
12
|
+
if (typeof v !== 'string')
|
|
13
|
+
return undefined;
|
|
14
|
+
const m = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v);
|
|
15
|
+
if (m === null)
|
|
16
|
+
return undefined;
|
|
17
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
18
|
+
}
|
|
19
|
+
function compareTriple(a, b) {
|
|
20
|
+
for (let i = 0; i < 3; i++) {
|
|
21
|
+
const x = a[i];
|
|
22
|
+
const y = b[i];
|
|
23
|
+
if (x !== y)
|
|
24
|
+
return x < y ? -1 : 1;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
export function engineVersionTier(version, floor) {
|
|
29
|
+
let need = releaseTriple(SUPPORT_FLOOR.server);
|
|
30
|
+
if (floor !== undefined) {
|
|
31
|
+
const own = releaseTriple(floor);
|
|
32
|
+
if (own === undefined) {
|
|
33
|
+
throw new TypeError(`engineVersionTier: floor 只接受纯 x.y.z 版本串(整串、无前导零、无前后缀)或缺席 —— 收到 typeof=${floor === null ? 'null' : typeof floor}。错误文本只报形状,不回显值。`);
|
|
34
|
+
}
|
|
35
|
+
if (compareTriple(own, need) > 0)
|
|
36
|
+
need = own;
|
|
37
|
+
}
|
|
38
|
+
const got = releaseTriple(version);
|
|
39
|
+
if (got === undefined)
|
|
40
|
+
return 'unknown';
|
|
41
|
+
return compareTriple(got, need) >= 0 ? 'engine' : 'legacy';
|
|
42
|
+
}
|
|
@@ -3,3 +3,10 @@ export type TaskRequestReasoningEffort = NonNullable<TaskRequest['reasoningEffor
|
|
|
3
3
|
export declare const REASONING_EFFORT_WORDS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
4
4
|
export type TaskRequestPermissionMode = NonNullable<TaskRequest['permissionMode']>;
|
|
5
5
|
export declare const PERMISSION_MODE_WORDS: readonly ["default", "plan", "acceptEdits", "bypassPermissions", "auto"];
|
|
6
|
+
export interface RetiredAttachmentsKey {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly why: string;
|
|
9
|
+
readonly because: string;
|
|
10
|
+
readonly remedy: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function retiredAttachmentsKeyOf(nameOrToken: string, form?: 'token' | 'key'): RetiredAttachmentsKey | undefined;
|
package/dist/taskRequestWords.js
CHANGED
|
@@ -2,3 +2,27 @@ export const REASONING_EFFORT_WORDS = ['off', 'minimal', 'low', 'medium', 'high'
|
|
|
2
2
|
export const PERMISSION_MODE_WORDS = ['default', 'plan', 'acceptEdits', 'bypassPermissions', 'auto'];
|
|
3
3
|
const _taskRequestWordPins = [true, true, true, true];
|
|
4
4
|
void _taskRequestWordPins;
|
|
5
|
+
export function retiredAttachmentsKeyOf(nameOrToken, form = 'token') {
|
|
6
|
+
const lower = nameOrToken.trim().toLowerCase();
|
|
7
|
+
const eq = lower.indexOf('=');
|
|
8
|
+
const lhs = (eq < 0 ? lower : lower.slice(0, eq)).trim();
|
|
9
|
+
const bare = lhs.startsWith('+') || lhs.startsWith('-') ? lhs.slice(1).trim() : lhs;
|
|
10
|
+
const retiredRows = [
|
|
11
|
+
{
|
|
12
|
+
key: 'planModeReminder',
|
|
13
|
+
since: 'core 7.39.0 / server 7.113.0',
|
|
14
|
+
because: 'the engine now states the plan posture itself whenever the permission mode is "plan"',
|
|
15
|
+
remedy: 'send permissionMode "plan" for the plan posture',
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const row = retiredRows.find((r) => r.key.toLowerCase() === bare);
|
|
19
|
+
if (row === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const head = `"${row.key}" was retired with ${row.since} — ${row.because}`;
|
|
22
|
+
const why = form === 'token'
|
|
23
|
+
? `${head}, and a request carrying the key is answered 400 request.field_invalid; remove the token (${row.remedy})`
|
|
24
|
+
: nameOrToken === row.key
|
|
25
|
+
? `${head}, and a request carrying this key is answered 400 request.field_invalid; remove the key (${row.remedy})`
|
|
26
|
+
: `${head}; the server ignores this spelling of it, but it is refused here so the retired key is not sent under any spelling; remove the key (${row.remedy})`;
|
|
27
|
+
return { key: row.key, why, because: row.because, remedy: row.remedy };
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toolHistoryBadTurnSentence(promptText: unknown): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { displayUntrusted } from './displayUntrusted.js';
|
|
2
|
+
const BAD_TURN_PREVIEW_MAX = 60;
|
|
3
|
+
export function toolHistoryBadTurnSentence(promptText) {
|
|
4
|
+
const text = typeof promptText === 'string' ? promptText : '';
|
|
5
|
+
const flat = stripDisplayTagBlocks(text).replace(/\s+/g, ' ').trim();
|
|
6
|
+
const washed = displayUntrusted(flat, { foldLines: false, mark: 'dot' });
|
|
7
|
+
const points = Array.from(washed);
|
|
8
|
+
const preview = points.length > BAD_TURN_PREVIEW_MAX ? `${points.slice(0, BAD_TURN_PREVIEW_MAX).join('')}...` : washed;
|
|
9
|
+
return preview.length > 0
|
|
10
|
+
? `The broken record is in the turn you started with "${preview}"; /rewind has that prompt preselected, so restore the conversation to before it.`
|
|
11
|
+
: 'The broken record is in the turn /rewind has preselected; restore the conversation to before it.';
|
|
12
|
+
}
|
|
13
|
+
function lowerBound(arr, p) {
|
|
14
|
+
let lo = 0;
|
|
15
|
+
let hi = arr.length;
|
|
16
|
+
while (lo < hi) {
|
|
17
|
+
const mid = (lo + hi) >>> 1;
|
|
18
|
+
const v = arr[mid];
|
|
19
|
+
if (v !== undefined && v < p)
|
|
20
|
+
lo = mid + 1;
|
|
21
|
+
else
|
|
22
|
+
hi = mid;
|
|
23
|
+
}
|
|
24
|
+
return lo;
|
|
25
|
+
}
|
|
26
|
+
function isTagNameChar(c) {
|
|
27
|
+
return (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95 || c === 45;
|
|
28
|
+
}
|
|
29
|
+
const TAG_WS = /\s/;
|
|
30
|
+
function stripDisplayTagBlocks(text) {
|
|
31
|
+
const n = text.length;
|
|
32
|
+
const closesByName = new Map();
|
|
33
|
+
for (let i = text.indexOf('</'); i >= 0; i = text.indexOf('</', i + 1)) {
|
|
34
|
+
let k = i + 2;
|
|
35
|
+
while (k < n && isTagNameChar(text.charCodeAt(k)))
|
|
36
|
+
k++;
|
|
37
|
+
if (k > i + 2 && text.charCodeAt(k) === 62) {
|
|
38
|
+
const name = text.slice(i + 2, k);
|
|
39
|
+
const bucket = closesByName.get(name);
|
|
40
|
+
if (bucket !== undefined)
|
|
41
|
+
bucket.push(i);
|
|
42
|
+
else
|
|
43
|
+
closesByName.set(name, [i]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (closesByName.size === 0)
|
|
47
|
+
return keepIfEmpty(text, text);
|
|
48
|
+
const gts = [];
|
|
49
|
+
for (let i = text.indexOf('>'); i >= 0; i = text.indexOf('>', i + 1))
|
|
50
|
+
gts.push(i);
|
|
51
|
+
let out = '';
|
|
52
|
+
let pos = 0;
|
|
53
|
+
for (let i = text.indexOf('<'); i >= 0;) {
|
|
54
|
+
const first = text.charCodeAt(i + 1);
|
|
55
|
+
if (!(first >= 97 && first <= 122)) {
|
|
56
|
+
i = text.indexOf('<', i + 1);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
let k = i + 2;
|
|
60
|
+
while (k < n && isTagNameChar(text.charCodeAt(k)))
|
|
61
|
+
k++;
|
|
62
|
+
const name = text.slice(i + 1, k);
|
|
63
|
+
let openEnd = -1;
|
|
64
|
+
if (text.charCodeAt(k) === 62)
|
|
65
|
+
openEnd = k + 1;
|
|
66
|
+
else if (k < n && TAG_WS.test(text.charAt(k))) {
|
|
67
|
+
const g = gts[lowerBound(gts, k)];
|
|
68
|
+
if (g !== undefined)
|
|
69
|
+
openEnd = g + 1;
|
|
70
|
+
}
|
|
71
|
+
const bucket = openEnd >= 0 ? closesByName.get(name) : undefined;
|
|
72
|
+
const close = bucket === undefined ? undefined : bucket[lowerBound(bucket, openEnd)];
|
|
73
|
+
if (close === undefined) {
|
|
74
|
+
i = text.indexOf('<', i + 1);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let end = close + name.length + 3;
|
|
78
|
+
if (text.charCodeAt(end) === 10)
|
|
79
|
+
end++;
|
|
80
|
+
out += text.slice(pos, i);
|
|
81
|
+
pos = end;
|
|
82
|
+
i = text.indexOf('<', end);
|
|
83
|
+
}
|
|
84
|
+
return keepIfEmpty(pos === 0 ? text : out + text.slice(pos), text);
|
|
85
|
+
}
|
|
86
|
+
function keepIfEmpty(stripped, original) {
|
|
87
|
+
const t = stripped.trim();
|
|
88
|
+
return t.length > 0 ? t : original;
|
|
89
|
+
}
|
|
@@ -27,8 +27,9 @@ export interface ToolHistoryMismatchRowKey {
|
|
|
27
27
|
kind: ToolHistoryMismatchKind;
|
|
28
28
|
toolUseId?: string;
|
|
29
29
|
consecutive?: number;
|
|
30
|
+
rewindOffered?: boolean;
|
|
30
31
|
}
|
|
31
|
-
export declare function toolHistoryMismatchRowKey(m: ToolHistoryMismatch, consecutive: number | undefined): ToolHistoryMismatchRowKey;
|
|
32
|
+
export declare function toolHistoryMismatchRowKey(m: ToolHistoryMismatch, consecutive: number | undefined, rewindOffered: boolean): ToolHistoryMismatchRowKey;
|
|
32
33
|
export declare function readToolHistoryMismatchKey(v: object | null | undefined): ToolHistoryMismatchRowKey | undefined;
|
|
33
34
|
export declare const TOOL_HISTORY_LEDGER_CAP = 64;
|
|
34
35
|
export declare function openToolHistoryGeneration(): number;
|
|
@@ -249,33 +249,46 @@ export function toolHistoryRewindOffered(declared) {
|
|
|
249
249
|
}
|
|
250
250
|
const isKind = (v) => typeof v === 'string' && TOOL_HISTORY_MISMATCH_KINDS.includes(v);
|
|
251
251
|
const ROW_KEY_ID = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
252
|
-
export function toolHistoryMismatchRowKey(m, consecutive) {
|
|
252
|
+
export function toolHistoryMismatchRowKey(m, consecutive, rewindOffered) {
|
|
253
253
|
return {
|
|
254
254
|
kind: m.kind,
|
|
255
255
|
...(m.toolUseId !== undefined ? { toolUseId: m.toolUseId } : {}),
|
|
256
256
|
...(consecutive !== undefined && consecutive >= 2 ? { consecutive } : {}),
|
|
257
|
+
rewindOffered,
|
|
257
258
|
};
|
|
258
259
|
}
|
|
259
260
|
export function readToolHistoryMismatchKey(v) {
|
|
260
|
-
if (typeof v !== 'object' || v === null
|
|
261
|
+
if (typeof v !== 'object' || v === null)
|
|
261
262
|
return undefined;
|
|
262
263
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
if (Array.isArray(v))
|
|
265
|
+
return undefined;
|
|
266
|
+
const kind = ownDataMember(v, 'kind');
|
|
265
267
|
if (!isKind(kind))
|
|
266
268
|
return undefined;
|
|
267
|
-
const id =
|
|
268
|
-
const n =
|
|
269
|
+
const id = ownDataMember(v, 'toolUseId');
|
|
270
|
+
const n = ownDataMember(v, 'consecutive');
|
|
271
|
+
const offered = ownDataMember(v, 'rewindOffered');
|
|
269
272
|
return {
|
|
270
273
|
kind,
|
|
271
274
|
...(typeof id === 'string' && ROW_KEY_ID.test(id) ? { toolUseId: id } : {}),
|
|
272
275
|
...(typeof n === 'number' && Number.isSafeInteger(n) && n >= 2 ? { consecutive: n } : {}),
|
|
276
|
+
...(typeof offered === 'boolean' ? { rewindOffered: offered } : {}),
|
|
273
277
|
};
|
|
274
278
|
}
|
|
275
279
|
catch {
|
|
276
280
|
return undefined;
|
|
277
281
|
}
|
|
278
282
|
}
|
|
283
|
+
function ownDataMember(o, k) {
|
|
284
|
+
try {
|
|
285
|
+
const d = Object.getOwnPropertyDescriptor(o, k);
|
|
286
|
+
return d !== undefined && Object.prototype.hasOwnProperty.call(d, 'value') ? d.value : undefined;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
279
292
|
export const TOOL_HISTORY_LEDGER_CAP = 64;
|
|
280
293
|
const PRIOR_RUN_IDS_CAP = 16;
|
|
281
294
|
const ECHOED_SESSION_IDS_CAP = TOOL_HISTORY_LEDGER_CAP * 4;
|