@elevasis/sdk 1.44.1 → 1.44.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +109 -89
- package/dist/index.js +10 -2
- package/dist/test-utils/index.js +97 -21
- package/dist/worker/index.js +97 -21
- package/package.json +3 -3
- package/reference/claude-config/sync-notes/2026-08-03-agent-prose-escape-normalization.md +88 -0
- package/reference/claude-config/sync-notes/2026-08-03-cli-gateway-errors-and-request-timeout.md +120 -0
- package/reference/sdk/resources/index.mdx +2 -0
package/dist/cli.cjs
CHANGED
|
@@ -43807,7 +43807,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
43807
43807
|
if (capabilities.message !== "off") {
|
|
43808
43808
|
properties.message = {
|
|
43809
43809
|
type: "string",
|
|
43810
|
-
description:
|
|
43810
|
+
description: 'Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
|
|
43811
43811
|
};
|
|
43812
43812
|
}
|
|
43813
43813
|
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
@@ -43827,7 +43827,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
43827
43827
|
type: "object",
|
|
43828
43828
|
properties: {
|
|
43829
43829
|
key: { type: "string" },
|
|
43830
|
-
value: {
|
|
43830
|
+
value: {
|
|
43831
|
+
type: "string",
|
|
43832
|
+
// `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
|
|
43833
|
+
// returns zero memory references -- so this description is the only framework-owned
|
|
43834
|
+
// lever on how a memory value is written. It matters more here than in `message`:
|
|
43835
|
+
// a session-memory value is re-sent on every iteration of every subsequent turn and
|
|
43836
|
+
// never ages out, so one literal escape stored here is one the model re-reads forever.
|
|
43837
|
+
description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
|
|
43838
|
+
}
|
|
43831
43839
|
},
|
|
43832
43840
|
required: ["key", "value"],
|
|
43833
43841
|
additionalProperties: false
|
|
@@ -44117,17 +44125,17 @@ function getLeadGenCrmHandoffResourceIds(model) {
|
|
|
44117
44125
|
(resource) => resource.systemPath === LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath && resource.ontology?.usesCatalogs?.includes(CRM_PIPELINE_CATALOG_ONTOLOGY_ID) === true
|
|
44118
44126
|
).map((resource) => resource.id);
|
|
44119
44127
|
}
|
|
44120
|
-
function getSystemInterfaceReadinessMarker(model,
|
|
44121
|
-
const system = getSystem(model,
|
|
44128
|
+
function getSystemInterfaceReadinessMarker(model, request2) {
|
|
44129
|
+
const system = getSystem(model, request2.systemPath);
|
|
44122
44130
|
if (system === void 0) return void 0;
|
|
44123
|
-
if (
|
|
44131
|
+
if (request2.systemPath === LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath && request2.interfaceKey === LEAD_GEN_CRM_HANDOFF_INTERFACE.interfaceKey) {
|
|
44124
44132
|
return {
|
|
44125
44133
|
lifecycle: "active",
|
|
44126
44134
|
readinessProfile: LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile,
|
|
44127
44135
|
resourceIds: getLeadGenCrmHandoffResourceIds(model)
|
|
44128
44136
|
};
|
|
44129
44137
|
}
|
|
44130
|
-
if (
|
|
44138
|
+
if (request2.interfaceKey === "api" && system.apiInterface !== void 0) {
|
|
44131
44139
|
return system.apiInterface;
|
|
44132
44140
|
}
|
|
44133
44141
|
return void 0;
|
|
@@ -44186,24 +44194,24 @@ function requireContractReadiness(issues, index, resources, contract, context) {
|
|
|
44186
44194
|
requireScopedBinding(issues, catalogs, "usesCatalogs", catalogId, context);
|
|
44187
44195
|
}
|
|
44188
44196
|
}
|
|
44189
|
-
function computeInterfaceReadiness(model,
|
|
44197
|
+
function computeInterfaceReadiness(model, request2) {
|
|
44190
44198
|
const issues = [];
|
|
44191
|
-
const system = getSystem(model,
|
|
44192
|
-
const systemInterface = getSystemInterfaceReadinessMarker(model,
|
|
44193
|
-
const readinessProfile = systemInterface === void 0 ? void 0 : profileForInterface(
|
|
44199
|
+
const system = getSystem(model, request2.systemPath);
|
|
44200
|
+
const systemInterface = getSystemInterfaceReadinessMarker(model, request2);
|
|
44201
|
+
const readinessProfile = systemInterface === void 0 ? void 0 : profileForInterface(request2.systemPath, request2.interfaceKey, systemInterface.readinessProfile);
|
|
44194
44202
|
const scopedResourceIds = systemInterface?.resourceIds ?? [];
|
|
44195
44203
|
if (system === void 0) {
|
|
44196
44204
|
addReadinessIssue(
|
|
44197
44205
|
issues,
|
|
44198
44206
|
"SYSTEM_INTERFACE_MISSING",
|
|
44199
44207
|
"missing-system",
|
|
44200
|
-
`System "${
|
|
44201
|
-
{ ref:
|
|
44208
|
+
`System "${request2.systemPath}" is missing.`,
|
|
44209
|
+
{ ref: request2.systemPath }
|
|
44202
44210
|
);
|
|
44203
44211
|
return {
|
|
44204
44212
|
ready: false,
|
|
44205
|
-
systemPath:
|
|
44206
|
-
interfaceKey:
|
|
44213
|
+
systemPath: request2.systemPath,
|
|
44214
|
+
interfaceKey: request2.interfaceKey,
|
|
44207
44215
|
scopedResourceIds,
|
|
44208
44216
|
issues
|
|
44209
44217
|
};
|
|
@@ -44213,13 +44221,13 @@ function computeInterfaceReadiness(model, request) {
|
|
|
44213
44221
|
issues,
|
|
44214
44222
|
"SYSTEM_INTERFACE_MISSING",
|
|
44215
44223
|
"missing-interface",
|
|
44216
|
-
`System "${
|
|
44217
|
-
{ path: readinessMarkerPath(
|
|
44224
|
+
`System "${request2.systemPath}" does not declare interface "${request2.interfaceKey}".`,
|
|
44225
|
+
{ path: readinessMarkerPath(request2) }
|
|
44218
44226
|
);
|
|
44219
44227
|
return {
|
|
44220
44228
|
ready: false,
|
|
44221
|
-
systemPath:
|
|
44222
|
-
interfaceKey:
|
|
44229
|
+
systemPath: request2.systemPath,
|
|
44230
|
+
interfaceKey: request2.interfaceKey,
|
|
44223
44231
|
scopedResourceIds,
|
|
44224
44232
|
issues
|
|
44225
44233
|
};
|
|
@@ -44229,11 +44237,11 @@ function computeInterfaceReadiness(model, request) {
|
|
|
44229
44237
|
issues,
|
|
44230
44238
|
"SYSTEM_INTERFACE_DISABLED",
|
|
44231
44239
|
"inactive-interface",
|
|
44232
|
-
`System Interface "${formatInterfaceIdentity(
|
|
44233
|
-
{ path: `${readinessMarkerPath(
|
|
44240
|
+
`System Interface "${formatInterfaceIdentity(request2.systemPath, request2.interfaceKey)}" lifecycle is "${systemInterface.lifecycle}".`,
|
|
44241
|
+
{ path: `${readinessMarkerPath(request2)}.lifecycle` }
|
|
44234
44242
|
);
|
|
44235
44243
|
}
|
|
44236
|
-
const checkedReadinessProfile = readinessProfile ?? profileForInterface(
|
|
44244
|
+
const checkedReadinessProfile = readinessProfile ?? profileForInterface(request2.systemPath, request2.interfaceKey);
|
|
44237
44245
|
const isBuiltIn = isBuiltInReadinessProfile(checkedReadinessProfile);
|
|
44238
44246
|
const readinessContract = systemInterface.readinessContract;
|
|
44239
44247
|
if (!isBuiltIn && readinessContract === void 0) {
|
|
@@ -44241,13 +44249,13 @@ function computeInterfaceReadiness(model, request) {
|
|
|
44241
44249
|
issues,
|
|
44242
44250
|
"SYSTEM_INTERFACE_INVALID",
|
|
44243
44251
|
"missing-readiness-contract",
|
|
44244
|
-
`System Interface "${formatInterfaceIdentity(
|
|
44245
|
-
{ path: `${readinessMarkerPath(
|
|
44252
|
+
`System Interface "${formatInterfaceIdentity(request2.systemPath, request2.interfaceKey)}" uses custom readiness profile "${checkedReadinessProfile}" but declares no readinessContract. Built-in profiles: ${formatSupportedReadinessProfiles()}.`,
|
|
44253
|
+
{ path: `${readinessMarkerPath(request2)}.readinessContract`, ref: checkedReadinessProfile }
|
|
44246
44254
|
);
|
|
44247
44255
|
return {
|
|
44248
44256
|
ready: false,
|
|
44249
|
-
systemPath:
|
|
44250
|
-
interfaceKey:
|
|
44257
|
+
systemPath: request2.systemPath,
|
|
44258
|
+
interfaceKey: request2.interfaceKey,
|
|
44251
44259
|
readinessProfile: checkedReadinessProfile,
|
|
44252
44260
|
scopedResourceIds,
|
|
44253
44261
|
issues
|
|
@@ -44258,29 +44266,29 @@ function computeInterfaceReadiness(model, request) {
|
|
|
44258
44266
|
issues,
|
|
44259
44267
|
"SYSTEM_INTERFACE_NOT_READY",
|
|
44260
44268
|
"missing-scoped-resources",
|
|
44261
|
-
`System Interface "${formatInterfaceIdentity(
|
|
44262
|
-
{ path: `${readinessMarkerPath(
|
|
44269
|
+
`System Interface "${formatInterfaceIdentity(request2.systemPath, request2.interfaceKey)}" must scope at least one active resource.`,
|
|
44270
|
+
{ path: `${readinessMarkerPath(request2)}.resourceIds` }
|
|
44263
44271
|
);
|
|
44264
44272
|
}
|
|
44265
|
-
const resources = getActiveScopedResources(model, scopedResourceIds, issues,
|
|
44273
|
+
const resources = getActiveScopedResources(model, scopedResourceIds, issues, request2);
|
|
44266
44274
|
const index = tryCompileBusinessOntology(model, checkedReadinessProfile, issues);
|
|
44267
44275
|
if (index !== void 0) {
|
|
44268
44276
|
if (checkedReadinessProfile === LEAD_GEN_API_INTERFACE.readinessProfile) {
|
|
44269
|
-
requireLeadGenInterfaceReadiness(issues, index, resources,
|
|
44277
|
+
requireLeadGenInterfaceReadiness(issues, index, resources, request2);
|
|
44270
44278
|
} else if (checkedReadinessProfile === CRM_API_INTERFACE.readinessProfile) {
|
|
44271
|
-
requireCrmInterfaceReadiness(issues, index, resources,
|
|
44279
|
+
requireCrmInterfaceReadiness(issues, index, resources, request2);
|
|
44272
44280
|
} else if (checkedReadinessProfile === LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile) {
|
|
44273
|
-
requireLeadGenInterfaceReadiness(issues, index, resources,
|
|
44274
|
-
requireCrmInterfaceReadiness(issues, index, resources, { ...
|
|
44275
|
-
requireHandoffBridgeReadiness(issues, model,
|
|
44281
|
+
requireLeadGenInterfaceReadiness(issues, index, resources, request2);
|
|
44282
|
+
requireCrmInterfaceReadiness(issues, index, resources, { ...request2, allowForeignOwner: true });
|
|
44283
|
+
requireHandoffBridgeReadiness(issues, model, request2);
|
|
44276
44284
|
} else if (readinessContract !== void 0) {
|
|
44277
|
-
requireContractReadiness(issues, index, resources, readinessContract,
|
|
44285
|
+
requireContractReadiness(issues, index, resources, readinessContract, request2);
|
|
44278
44286
|
}
|
|
44279
44287
|
}
|
|
44280
44288
|
return {
|
|
44281
44289
|
ready: issues.length === 0,
|
|
44282
|
-
systemPath:
|
|
44283
|
-
interfaceKey:
|
|
44290
|
+
systemPath: request2.systemPath,
|
|
44291
|
+
interfaceKey: request2.interfaceKey,
|
|
44284
44292
|
readinessProfile: checkedReadinessProfile,
|
|
44285
44293
|
scopedResourceIds,
|
|
44286
44294
|
issues
|
|
@@ -46160,22 +46168,72 @@ function getApiKey(apiUrl) {
|
|
|
46160
46168
|
const key = resolveApiKey(isProd);
|
|
46161
46169
|
if (!key) {
|
|
46162
46170
|
const varName = isProd ? "ELEVASIS_PLATFORM_KEY" : "ELEVASIS_PLATFORM_KEY_DEV";
|
|
46163
|
-
throw new Error(
|
|
46164
|
-
|
|
46165
|
-
Set it in your .env file: ${varName}=sk_...`
|
|
46166
|
-
);
|
|
46171
|
+
throw new Error(`${varName} environment variable is required.
|
|
46172
|
+
Set it in your .env file: ${varName}=sk_...`);
|
|
46167
46173
|
}
|
|
46168
46174
|
return key;
|
|
46169
46175
|
}
|
|
46170
|
-
|
|
46171
|
-
|
|
46172
|
-
|
|
46173
|
-
|
|
46176
|
+
var DEFAULT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
|
|
46177
|
+
function resolveTimeoutMs() {
|
|
46178
|
+
const raw = process.env.ELEVASIS_CLI_TIMEOUT_MS;
|
|
46179
|
+
if (!raw) return DEFAULT_TIMEOUT_MS;
|
|
46180
|
+
const parsed = Number(raw);
|
|
46181
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
|
|
46182
|
+
}
|
|
46183
|
+
function formatElapsed(ms) {
|
|
46184
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
46185
|
+
}
|
|
46186
|
+
var GATEWAY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
46187
|
+
function describeGatewayFailure(status, endpoint, elapsedMs) {
|
|
46188
|
+
return `API request failed (${status}) after ${formatElapsed(elapsedMs)}: a gateway between this CLI and the API ended the connection.
|
|
46189
|
+
This is NOT the API rejecting the request, and the work may still be running server-side -- a status code from a proxy says nothing about whether the handler completed.
|
|
46190
|
+
Endpoint: ${endpoint}
|
|
46191
|
+
Check for a result before re-running, or the same work may execute twice:
|
|
46192
|
+
executions -> 'elevasis execution <workflow> <id>'
|
|
46193
|
+
session turns -> 'elevasis-sdk session:messages <session-id> --all'
|
|
46194
|
+
Workflow executions can avoid this entirely with 'exec --async', which returns immediately
|
|
46195
|
+
and polls. Session turns have no async variant, so a long turn is exposed to this cutoff.`;
|
|
46196
|
+
}
|
|
46197
|
+
async function request(method, endpoint, apiUrl, body) {
|
|
46198
|
+
const timeoutMs = resolveTimeoutMs();
|
|
46199
|
+
const startedAt = Date.now();
|
|
46200
|
+
const headers = { Authorization: `Bearer ${getApiKey(apiUrl)}` };
|
|
46201
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
46202
|
+
let response;
|
|
46203
|
+
try {
|
|
46204
|
+
response = await fetch(`${apiUrl}${endpoint}`, {
|
|
46205
|
+
method,
|
|
46206
|
+
headers,
|
|
46207
|
+
...body !== void 0 && { body: JSON.stringify(body) },
|
|
46208
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
46209
|
+
});
|
|
46210
|
+
} catch (error46) {
|
|
46211
|
+
const elapsedMs = Date.now() - startedAt;
|
|
46212
|
+
if (error46 instanceof Error && error46.name === "TimeoutError") {
|
|
46213
|
+
throw new Error(
|
|
46214
|
+
`Request timed out after ${formatElapsed(elapsedMs)} (limit ${formatElapsed(timeoutMs)}).
|
|
46215
|
+
Endpoint: ${endpoint}
|
|
46216
|
+
The server may still be working -- check for a result before re-running.
|
|
46217
|
+
Raise the limit with ELEVASIS_CLI_TIMEOUT_MS, or, for a workflow execution, submit it
|
|
46218
|
+
with 'exec --async'. Session turns have no async variant.`
|
|
46219
|
+
);
|
|
46220
|
+
}
|
|
46221
|
+
const detail = error46 instanceof Error ? error46.message : String(error46);
|
|
46222
|
+
throw new Error(
|
|
46223
|
+
`Could not reach the API after ${formatElapsed(elapsedMs)}: ${detail}
|
|
46224
|
+
Endpoint: ${apiUrl}${endpoint}
|
|
46225
|
+
No HTTP status was returned, so the request may never have been received.`
|
|
46226
|
+
);
|
|
46227
|
+
}
|
|
46174
46228
|
if (!response.ok) {
|
|
46229
|
+
const elapsedMs = Date.now() - startedAt;
|
|
46175
46230
|
const errorText = await response.text();
|
|
46176
46231
|
if (response.status === 401) {
|
|
46177
46232
|
throw new Error(`401 Unauthorized: Invalid or missing ELEVASIS_PLATFORM_KEY. Check your .env file.`);
|
|
46178
46233
|
}
|
|
46234
|
+
if (GATEWAY_STATUSES.has(response.status)) {
|
|
46235
|
+
throw new Error(describeGatewayFailure(response.status, endpoint, elapsedMs));
|
|
46236
|
+
}
|
|
46179
46237
|
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
46180
46238
|
}
|
|
46181
46239
|
if (response.status === 204) {
|
|
@@ -46183,55 +46241,17 @@ async function apiGet(endpoint, apiUrl = resolveApiUrl()) {
|
|
|
46183
46241
|
}
|
|
46184
46242
|
return response.json();
|
|
46185
46243
|
}
|
|
46244
|
+
async function apiGet(endpoint, apiUrl = resolveApiUrl()) {
|
|
46245
|
+
return request("GET", endpoint, apiUrl);
|
|
46246
|
+
}
|
|
46186
46247
|
async function apiPost(endpoint, body, apiUrl = resolveApiUrl()) {
|
|
46187
|
-
|
|
46188
|
-
method: "POST",
|
|
46189
|
-
headers: {
|
|
46190
|
-
Authorization: `Bearer ${getApiKey(apiUrl)}`,
|
|
46191
|
-
"Content-Type": "application/json"
|
|
46192
|
-
},
|
|
46193
|
-
body: JSON.stringify(body)
|
|
46194
|
-
});
|
|
46195
|
-
if (!response.ok) {
|
|
46196
|
-
const errorText = await response.text();
|
|
46197
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
46198
|
-
}
|
|
46199
|
-
if (response.status === 204) {
|
|
46200
|
-
return {};
|
|
46201
|
-
}
|
|
46202
|
-
return response.json();
|
|
46248
|
+
return request("POST", endpoint, apiUrl, body);
|
|
46203
46249
|
}
|
|
46204
46250
|
async function apiPatch(endpoint, body, apiUrl = resolveApiUrl()) {
|
|
46205
|
-
|
|
46206
|
-
method: "PATCH",
|
|
46207
|
-
headers: {
|
|
46208
|
-
Authorization: `Bearer ${getApiKey(apiUrl)}`,
|
|
46209
|
-
"Content-Type": "application/json"
|
|
46210
|
-
},
|
|
46211
|
-
body: JSON.stringify(body)
|
|
46212
|
-
});
|
|
46213
|
-
if (!response.ok) {
|
|
46214
|
-
const errorText = await response.text();
|
|
46215
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
46216
|
-
}
|
|
46217
|
-
if (response.status === 204) {
|
|
46218
|
-
return {};
|
|
46219
|
-
}
|
|
46220
|
-
return response.json();
|
|
46251
|
+
return request("PATCH", endpoint, apiUrl, body);
|
|
46221
46252
|
}
|
|
46222
46253
|
async function apiDelete(endpoint, apiUrl = resolveApiUrl()) {
|
|
46223
|
-
|
|
46224
|
-
method: "DELETE",
|
|
46225
|
-
headers: { Authorization: `Bearer ${getApiKey(apiUrl)}` }
|
|
46226
|
-
});
|
|
46227
|
-
if (!response.ok) {
|
|
46228
|
-
const errorText = await response.text();
|
|
46229
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
46230
|
-
}
|
|
46231
|
-
if (response.status === 204) {
|
|
46232
|
-
return {};
|
|
46233
|
-
}
|
|
46234
|
-
return response.json();
|
|
46254
|
+
return request("DELETE", endpoint, apiUrl);
|
|
46235
46255
|
}
|
|
46236
46256
|
|
|
46237
46257
|
// src/cli/wrap-action.ts
|
|
@@ -46253,7 +46273,7 @@ function wrapAction(commandName, fn) {
|
|
|
46253
46273
|
// package.json
|
|
46254
46274
|
var package_default = {
|
|
46255
46275
|
name: "@elevasis/sdk",
|
|
46256
|
-
version: "1.44.
|
|
46276
|
+
version: "1.44.3",
|
|
46257
46277
|
description: "SDK for building Elevasis organization resources",
|
|
46258
46278
|
type: "module",
|
|
46259
46279
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -4021,7 +4021,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
4021
4021
|
if (capabilities.message !== "off") {
|
|
4022
4022
|
properties.message = {
|
|
4023
4023
|
type: "string",
|
|
4024
|
-
description:
|
|
4024
|
+
description: 'Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
|
|
4025
4025
|
};
|
|
4026
4026
|
}
|
|
4027
4027
|
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
@@ -4041,7 +4041,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
4041
4041
|
type: "object",
|
|
4042
4042
|
properties: {
|
|
4043
4043
|
key: { type: "string" },
|
|
4044
|
-
value: {
|
|
4044
|
+
value: {
|
|
4045
|
+
type: "string",
|
|
4046
|
+
// `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
|
|
4047
|
+
// returns zero memory references -- so this description is the only framework-owned
|
|
4048
|
+
// lever on how a memory value is written. It matters more here than in `message`:
|
|
4049
|
+
// a session-memory value is re-sent on every iteration of every subsequent turn and
|
|
4050
|
+
// never ages out, so one literal escape stored here is one the model re-reads forever.
|
|
4051
|
+
description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
|
|
4052
|
+
}
|
|
4045
4053
|
},
|
|
4046
4054
|
required: ["key", "value"],
|
|
4047
4055
|
additionalProperties: false
|
package/dist/test-utils/index.js
CHANGED
|
@@ -4479,6 +4479,11 @@ every response:
|
|
|
4479
4479
|
and never continue the response envelope in the reasoning text -- nextActions is a separate field
|
|
4480
4480
|
that you fill separately. A response carrying reasoning alone is discarded and retried.
|
|
4481
4481
|
|
|
4482
|
+
**Prose fields take real characters, never escape sequences.** A line break in ${includeMessage ? "message or reasoning" : "reasoning"} is
|
|
4483
|
+
a real line break -- not the two characters \\n. A quotation mark is the character ", not \\". The
|
|
4484
|
+
response is serialized for you; typing the escape yourself puts those literal characters into the
|
|
4485
|
+
stored text and in front of the reader.
|
|
4486
|
+
|
|
4482
4487
|
## Rules
|
|
4483
4488
|
|
|
4484
4489
|
- Batch independent tool calls in one iteration (faster execution)
|
|
@@ -5074,7 +5079,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
5074
5079
|
if (capabilities.message !== "off") {
|
|
5075
5080
|
properties.message = {
|
|
5076
5081
|
type: "string",
|
|
5077
|
-
description:
|
|
5082
|
+
description: 'Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
|
|
5078
5083
|
};
|
|
5079
5084
|
}
|
|
5080
5085
|
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
@@ -5094,7 +5099,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
5094
5099
|
type: "object",
|
|
5095
5100
|
properties: {
|
|
5096
5101
|
key: { type: "string" },
|
|
5097
|
-
value: {
|
|
5102
|
+
value: {
|
|
5103
|
+
type: "string",
|
|
5104
|
+
// `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
|
|
5105
|
+
// returns zero memory references -- so this description is the only framework-owned
|
|
5106
|
+
// lever on how a memory value is written. It matters more here than in `message`:
|
|
5107
|
+
// a session-memory value is re-sent on every iteration of every subsequent turn and
|
|
5108
|
+
// never ages out, so one literal escape stored here is one the model re-reads forever.
|
|
5109
|
+
description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
|
|
5110
|
+
}
|
|
5098
5111
|
},
|
|
5099
5112
|
required: ["key", "value"],
|
|
5100
5113
|
additionalProperties: false
|
|
@@ -5113,6 +5126,59 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
5113
5126
|
};
|
|
5114
5127
|
}
|
|
5115
5128
|
|
|
5129
|
+
// ../core/src/execution/engine/agent/reasoning/adapters/prose-escapes.ts
|
|
5130
|
+
var BACKSLASH = String.fromCharCode(92);
|
|
5131
|
+
function normalizeProseEscapes(text) {
|
|
5132
|
+
if (!text.includes(BACKSLASH)) return text;
|
|
5133
|
+
let out = "";
|
|
5134
|
+
let i = 0;
|
|
5135
|
+
while (i < text.length) {
|
|
5136
|
+
const char = text[i];
|
|
5137
|
+
if (char !== BACKSLASH || i + 1 >= text.length) {
|
|
5138
|
+
out += char;
|
|
5139
|
+
i += 1;
|
|
5140
|
+
continue;
|
|
5141
|
+
}
|
|
5142
|
+
switch (text[i + 1]) {
|
|
5143
|
+
case BACKSLASH:
|
|
5144
|
+
out += BACKSLASH + BACKSLASH;
|
|
5145
|
+
i += 2;
|
|
5146
|
+
break;
|
|
5147
|
+
case "n":
|
|
5148
|
+
out += "\n";
|
|
5149
|
+
i += 2;
|
|
5150
|
+
break;
|
|
5151
|
+
case "r":
|
|
5152
|
+
if (text[i + 2] === BACKSLASH && text[i + 3] === "n") {
|
|
5153
|
+
out += "\n";
|
|
5154
|
+
i += 4;
|
|
5155
|
+
} else {
|
|
5156
|
+
out += "\n";
|
|
5157
|
+
i += 2;
|
|
5158
|
+
}
|
|
5159
|
+
break;
|
|
5160
|
+
case "t":
|
|
5161
|
+
out += " ";
|
|
5162
|
+
i += 2;
|
|
5163
|
+
break;
|
|
5164
|
+
case '"':
|
|
5165
|
+
out += '"';
|
|
5166
|
+
i += 2;
|
|
5167
|
+
break;
|
|
5168
|
+
default:
|
|
5169
|
+
out += char;
|
|
5170
|
+
i += 1;
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
return out;
|
|
5174
|
+
}
|
|
5175
|
+
function normalizeMemoryValue(value) {
|
|
5176
|
+
if (typeof value !== "string") return value;
|
|
5177
|
+
const trimmed = value.trim();
|
|
5178
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return value;
|
|
5179
|
+
return normalizeProseEscapes(value);
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5116
5182
|
// ../core/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts
|
|
5117
5183
|
var MemoryKeyValuePairSchema = z.object({ key: z.string(), value: z.any() });
|
|
5118
5184
|
var MemorySetSchema = z.union([z.record(z.string(), z.any()), z.array(MemoryKeyValuePairSchema)]).transform((value) => {
|
|
@@ -5124,13 +5190,33 @@ var MemoryOperationsSchema = z.object({
|
|
|
5124
5190
|
// Accept any value type - framework will stringify
|
|
5125
5191
|
delete: z.array(z.string()).optional()
|
|
5126
5192
|
});
|
|
5127
|
-
var
|
|
5193
|
+
var AgentIterationOutputObjectSchema = z.object({
|
|
5128
5194
|
reasoning: z.string(),
|
|
5129
5195
|
message: z.string().optional(),
|
|
5130
5196
|
memoryOps: MemoryOperationsSchema.optional(),
|
|
5131
5197
|
nextActions: z.array(AgentActionSchema)
|
|
5132
5198
|
});
|
|
5133
|
-
|
|
5199
|
+
function normalizeIterationProse(output) {
|
|
5200
|
+
const normalized = { ...output };
|
|
5201
|
+
normalized.reasoning = normalizeProseEscapes(output.reasoning);
|
|
5202
|
+
if (typeof output.message === "string") {
|
|
5203
|
+
normalized.message = normalizeProseEscapes(output.message);
|
|
5204
|
+
}
|
|
5205
|
+
if (output.memoryOps?.set) {
|
|
5206
|
+
normalized.memoryOps = {
|
|
5207
|
+
...output.memoryOps,
|
|
5208
|
+
set: Object.fromEntries(
|
|
5209
|
+
Object.entries(output.memoryOps.set).map(([key, value]) => [key, normalizeMemoryValue(value)])
|
|
5210
|
+
)
|
|
5211
|
+
};
|
|
5212
|
+
}
|
|
5213
|
+
normalized.nextActions = output.nextActions.map(
|
|
5214
|
+
(action) => action.type === "message" ? { ...action, text: normalizeProseEscapes(action.text) } : action
|
|
5215
|
+
);
|
|
5216
|
+
return normalized;
|
|
5217
|
+
}
|
|
5218
|
+
var AgentIterationOutputSchema = AgentIterationOutputObjectSchema.transform(normalizeIterationProse);
|
|
5219
|
+
var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputObjectSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
|
|
5134
5220
|
function withSynthesizedMessage(nextActions, message) {
|
|
5135
5221
|
const text = message?.trim();
|
|
5136
5222
|
if (!text) {
|
|
@@ -5295,6 +5381,10 @@ var MEMORY_DOMAINS = {
|
|
|
5295
5381
|
*
|
|
5296
5382
|
* Tools manage complex structured data (caches, indexes, etc.)
|
|
5297
5383
|
* that would be error-prone for LLM to update via JSON strings.
|
|
5384
|
+
*
|
|
5385
|
+
* No tool currently writes these three keys, so in practice the guard reserves the names
|
|
5386
|
+
* against the LLM rather than arbitrating a live conflict. That is still enforcement --
|
|
5387
|
+
* see `validateMemoryKeyOwnership` in `./utils` -- not dead code.
|
|
5298
5388
|
*/
|
|
5299
5389
|
TOOL_OWNED: [
|
|
5300
5390
|
"notion-pages-cache",
|
|
@@ -5303,20 +5393,10 @@ var MEMORY_DOMAINS = {
|
|
|
5303
5393
|
// GitHub tools manage repository structure
|
|
5304
5394
|
"slack-channel-cache"
|
|
5305
5395
|
// Slack tools manage channel data
|
|
5306
|
-
]
|
|
5307
|
-
/**
|
|
5308
|
-
* Action-owned keys
|
|
5309
|
-
* Updated by framework actions
|
|
5310
|
-
* LLM cannot modify these via memoryOps
|
|
5311
|
-
*
|
|
5312
|
-
* Actions manage framework state that controls execution flow. Empty today -- the one
|
|
5313
|
-
* action that ever wrote here was retired. Kept as its own domain because a future
|
|
5314
|
-
* action-managed key belongs here, not folded into TOOL_OWNED.
|
|
5315
|
-
*/
|
|
5316
|
-
ACTION_OWNED: []
|
|
5396
|
+
]
|
|
5317
5397
|
/**
|
|
5318
5398
|
* LLM-owned keys
|
|
5319
|
-
* All keys NOT in TOOL_OWNED
|
|
5399
|
+
* All keys NOT in TOOL_OWNED
|
|
5320
5400
|
* Managed via memoryOps by agent reasoning
|
|
5321
5401
|
*
|
|
5322
5402
|
* LLM manages semantic memory (business context, decisions, etc.)
|
|
@@ -5326,12 +5406,8 @@ var MEMORY_DOMAINS = {
|
|
|
5326
5406
|
function isToolOwnedKey(key) {
|
|
5327
5407
|
return MEMORY_DOMAINS.TOOL_OWNED.includes(key);
|
|
5328
5408
|
}
|
|
5329
|
-
function isActionOwnedKey(key) {
|
|
5330
|
-
return MEMORY_DOMAINS.ACTION_OWNED.includes(key);
|
|
5331
|
-
}
|
|
5332
5409
|
function getKeyOwner(key) {
|
|
5333
5410
|
if (isToolOwnedKey(key)) return "tool";
|
|
5334
|
-
if (isActionOwnedKey(key)) return "action";
|
|
5335
5411
|
return "llm";
|
|
5336
5412
|
}
|
|
5337
5413
|
|
|
@@ -5358,7 +5434,7 @@ function addToolError(memoryManager, action, errorMessage, iteration, turnNumber
|
|
|
5358
5434
|
});
|
|
5359
5435
|
}
|
|
5360
5436
|
function validateMemoryKeyOwnership(key, logger, iteration) {
|
|
5361
|
-
if (isToolOwnedKey(key)
|
|
5437
|
+
if (isToolOwnedKey(key)) {
|
|
5362
5438
|
const owner = getKeyOwner(key);
|
|
5363
5439
|
logger.action(
|
|
5364
5440
|
"memory-set-rejected",
|
package/dist/worker/index.js
CHANGED
|
@@ -2601,6 +2601,11 @@ every response:
|
|
|
2601
2601
|
and never continue the response envelope in the reasoning text -- nextActions is a separate field
|
|
2602
2602
|
that you fill separately. A response carrying reasoning alone is discarded and retried.
|
|
2603
2603
|
|
|
2604
|
+
**Prose fields take real characters, never escape sequences.** A line break in ${includeMessage ? "message or reasoning" : "reasoning"} is
|
|
2605
|
+
a real line break -- not the two characters \\n. A quotation mark is the character ", not \\". The
|
|
2606
|
+
response is serialized for you; typing the escape yourself puts those literal characters into the
|
|
2607
|
+
stored text and in front of the reader.
|
|
2608
|
+
|
|
2604
2609
|
## Rules
|
|
2605
2610
|
|
|
2606
2611
|
- Batch independent tool calls in one iteration (faster execution)
|
|
@@ -3166,7 +3171,7 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
3166
3171
|
if (capabilities.message !== "off") {
|
|
3167
3172
|
properties.message = {
|
|
3168
3173
|
type: "string",
|
|
3169
|
-
description:
|
|
3174
|
+
description: 'Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet. Write real line breaks and real quotation marks -- the two characters \\n are not a line break, and \\" is not a quotation mark. Serialization is handled for you.'
|
|
3170
3175
|
};
|
|
3171
3176
|
}
|
|
3172
3177
|
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
@@ -3186,7 +3191,15 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
3186
3191
|
type: "object",
|
|
3187
3192
|
properties: {
|
|
3188
3193
|
key: { type: "string" },
|
|
3189
|
-
value: {
|
|
3194
|
+
value: {
|
|
3195
|
+
type: "string",
|
|
3196
|
+
// `memoryOps` has NO framework prompt section -- a grep across `prompt-sections/`
|
|
3197
|
+
// returns zero memory references -- so this description is the only framework-owned
|
|
3198
|
+
// lever on how a memory value is written. It matters more here than in `message`:
|
|
3199
|
+
// a session-memory value is re-sent on every iteration of every subsequent turn and
|
|
3200
|
+
// never ages out, so one literal escape stored here is one the model re-reads forever.
|
|
3201
|
+
description: "Stored verbatim and replayed to you on every later turn. Write real line breaks and real quotation marks -- the two characters \\n are not a line break."
|
|
3202
|
+
}
|
|
3190
3203
|
},
|
|
3191
3204
|
required: ["key", "value"],
|
|
3192
3205
|
additionalProperties: false
|
|
@@ -3205,6 +3218,59 @@ function buildIterationResponseSchemaUncached(tools, capabilities) {
|
|
|
3205
3218
|
};
|
|
3206
3219
|
}
|
|
3207
3220
|
|
|
3221
|
+
// ../core/src/execution/engine/agent/reasoning/adapters/prose-escapes.ts
|
|
3222
|
+
var BACKSLASH = String.fromCharCode(92);
|
|
3223
|
+
function normalizeProseEscapes(text) {
|
|
3224
|
+
if (!text.includes(BACKSLASH)) return text;
|
|
3225
|
+
let out = "";
|
|
3226
|
+
let i = 0;
|
|
3227
|
+
while (i < text.length) {
|
|
3228
|
+
const char = text[i];
|
|
3229
|
+
if (char !== BACKSLASH || i + 1 >= text.length) {
|
|
3230
|
+
out += char;
|
|
3231
|
+
i += 1;
|
|
3232
|
+
continue;
|
|
3233
|
+
}
|
|
3234
|
+
switch (text[i + 1]) {
|
|
3235
|
+
case BACKSLASH:
|
|
3236
|
+
out += BACKSLASH + BACKSLASH;
|
|
3237
|
+
i += 2;
|
|
3238
|
+
break;
|
|
3239
|
+
case "n":
|
|
3240
|
+
out += "\n";
|
|
3241
|
+
i += 2;
|
|
3242
|
+
break;
|
|
3243
|
+
case "r":
|
|
3244
|
+
if (text[i + 2] === BACKSLASH && text[i + 3] === "n") {
|
|
3245
|
+
out += "\n";
|
|
3246
|
+
i += 4;
|
|
3247
|
+
} else {
|
|
3248
|
+
out += "\n";
|
|
3249
|
+
i += 2;
|
|
3250
|
+
}
|
|
3251
|
+
break;
|
|
3252
|
+
case "t":
|
|
3253
|
+
out += " ";
|
|
3254
|
+
i += 2;
|
|
3255
|
+
break;
|
|
3256
|
+
case '"':
|
|
3257
|
+
out += '"';
|
|
3258
|
+
i += 2;
|
|
3259
|
+
break;
|
|
3260
|
+
default:
|
|
3261
|
+
out += char;
|
|
3262
|
+
i += 1;
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
return out;
|
|
3266
|
+
}
|
|
3267
|
+
function normalizeMemoryValue(value) {
|
|
3268
|
+
if (typeof value !== "string") return value;
|
|
3269
|
+
const trimmed = value.trim();
|
|
3270
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return value;
|
|
3271
|
+
return normalizeProseEscapes(value);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3208
3274
|
// ../core/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts
|
|
3209
3275
|
var MemoryKeyValuePairSchema = z.object({ key: z.string(), value: z.any() });
|
|
3210
3276
|
var MemorySetSchema = z.union([z.record(z.string(), z.any()), z.array(MemoryKeyValuePairSchema)]).transform((value) => {
|
|
@@ -3216,13 +3282,33 @@ var MemoryOperationsSchema = z.object({
|
|
|
3216
3282
|
// Accept any value type - framework will stringify
|
|
3217
3283
|
delete: z.array(z.string()).optional()
|
|
3218
3284
|
});
|
|
3219
|
-
var
|
|
3285
|
+
var AgentIterationOutputObjectSchema = z.object({
|
|
3220
3286
|
reasoning: z.string(),
|
|
3221
3287
|
message: z.string().optional(),
|
|
3222
3288
|
memoryOps: MemoryOperationsSchema.optional(),
|
|
3223
3289
|
nextActions: z.array(AgentActionSchema)
|
|
3224
3290
|
});
|
|
3225
|
-
|
|
3291
|
+
function normalizeIterationProse(output) {
|
|
3292
|
+
const normalized = { ...output };
|
|
3293
|
+
normalized.reasoning = normalizeProseEscapes(output.reasoning);
|
|
3294
|
+
if (typeof output.message === "string") {
|
|
3295
|
+
normalized.message = normalizeProseEscapes(output.message);
|
|
3296
|
+
}
|
|
3297
|
+
if (output.memoryOps?.set) {
|
|
3298
|
+
normalized.memoryOps = {
|
|
3299
|
+
...output.memoryOps,
|
|
3300
|
+
set: Object.fromEntries(
|
|
3301
|
+
Object.entries(output.memoryOps.set).map(([key, value]) => [key, normalizeMemoryValue(value)])
|
|
3302
|
+
)
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
normalized.nextActions = output.nextActions.map(
|
|
3306
|
+
(action) => action.type === "message" ? { ...action, text: normalizeProseEscapes(action.text) } : action
|
|
3307
|
+
);
|
|
3308
|
+
return normalized;
|
|
3309
|
+
}
|
|
3310
|
+
var AgentIterationOutputSchema = AgentIterationOutputObjectSchema.transform(normalizeIterationProse);
|
|
3311
|
+
var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputObjectSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
|
|
3226
3312
|
function withSynthesizedMessage(nextActions, message) {
|
|
3227
3313
|
const text = message?.trim();
|
|
3228
3314
|
if (!text) {
|
|
@@ -3387,6 +3473,10 @@ var MEMORY_DOMAINS = {
|
|
|
3387
3473
|
*
|
|
3388
3474
|
* Tools manage complex structured data (caches, indexes, etc.)
|
|
3389
3475
|
* that would be error-prone for LLM to update via JSON strings.
|
|
3476
|
+
*
|
|
3477
|
+
* No tool currently writes these three keys, so in practice the guard reserves the names
|
|
3478
|
+
* against the LLM rather than arbitrating a live conflict. That is still enforcement --
|
|
3479
|
+
* see `validateMemoryKeyOwnership` in `./utils` -- not dead code.
|
|
3390
3480
|
*/
|
|
3391
3481
|
TOOL_OWNED: [
|
|
3392
3482
|
"notion-pages-cache",
|
|
@@ -3395,20 +3485,10 @@ var MEMORY_DOMAINS = {
|
|
|
3395
3485
|
// GitHub tools manage repository structure
|
|
3396
3486
|
"slack-channel-cache"
|
|
3397
3487
|
// Slack tools manage channel data
|
|
3398
|
-
]
|
|
3399
|
-
/**
|
|
3400
|
-
* Action-owned keys
|
|
3401
|
-
* Updated by framework actions
|
|
3402
|
-
* LLM cannot modify these via memoryOps
|
|
3403
|
-
*
|
|
3404
|
-
* Actions manage framework state that controls execution flow. Empty today -- the one
|
|
3405
|
-
* action that ever wrote here was retired. Kept as its own domain because a future
|
|
3406
|
-
* action-managed key belongs here, not folded into TOOL_OWNED.
|
|
3407
|
-
*/
|
|
3408
|
-
ACTION_OWNED: []
|
|
3488
|
+
]
|
|
3409
3489
|
/**
|
|
3410
3490
|
* LLM-owned keys
|
|
3411
|
-
* All keys NOT in TOOL_OWNED
|
|
3491
|
+
* All keys NOT in TOOL_OWNED
|
|
3412
3492
|
* Managed via memoryOps by agent reasoning
|
|
3413
3493
|
*
|
|
3414
3494
|
* LLM manages semantic memory (business context, decisions, etc.)
|
|
@@ -3418,12 +3498,8 @@ var MEMORY_DOMAINS = {
|
|
|
3418
3498
|
function isToolOwnedKey(key) {
|
|
3419
3499
|
return MEMORY_DOMAINS.TOOL_OWNED.includes(key);
|
|
3420
3500
|
}
|
|
3421
|
-
function isActionOwnedKey(key) {
|
|
3422
|
-
return MEMORY_DOMAINS.ACTION_OWNED.includes(key);
|
|
3423
|
-
}
|
|
3424
3501
|
function getKeyOwner(key) {
|
|
3425
3502
|
if (isToolOwnedKey(key)) return "tool";
|
|
3426
|
-
if (isActionOwnedKey(key)) return "action";
|
|
3427
3503
|
return "llm";
|
|
3428
3504
|
}
|
|
3429
3505
|
|
|
@@ -3450,7 +3526,7 @@ function addToolError(memoryManager, action, errorMessage, iteration, turnNumber
|
|
|
3450
3526
|
});
|
|
3451
3527
|
}
|
|
3452
3528
|
function validateMemoryKeyOwnership(key, logger, iteration) {
|
|
3453
|
-
if (isToolOwnedKey(key)
|
|
3529
|
+
if (isToolOwnedKey(key)) {
|
|
3454
3530
|
const owner = getKeyOwner(key);
|
|
3455
3531
|
logger.action(
|
|
3456
3532
|
"memory-set-rejected",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elevasis/sdk",
|
|
3
|
-
"version": "1.44.
|
|
3
|
+
"version": "1.44.3",
|
|
4
4
|
"description": "SDK for building Elevasis organization resources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"typescript": "5.9.2",
|
|
60
60
|
"zod": "^4.1.0",
|
|
61
61
|
"@repo/core": "0.59.0",
|
|
62
|
-
"@repo/
|
|
63
|
-
"@repo/
|
|
62
|
+
"@repo/typescript-config": "0.0.0",
|
|
63
|
+
"@repo/eslint-config": "0.0.0"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"lint": "eslint src --max-warnings 0",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Agent prose is repaired before it is stored
|
|
2
|
+
|
|
3
|
+
## Why this note exists
|
|
4
|
+
|
|
5
|
+
**Your agents have been writing the two characters `\n` into their replies where a line break
|
|
6
|
+
belongs, and `\"` where a quotation mark belongs.** The operator reads `\n\n` in the middle of a
|
|
7
|
+
sentence, and the stored transcript keeps those characters forever.
|
|
8
|
+
|
|
9
|
+
Measured on 2026-08-03 across four 14-turn production sessions on a live tenant agent — 56 assistant
|
|
10
|
+
replies:
|
|
11
|
+
|
|
12
|
+
| Surface | Rows | Rows affected | Literal `\n` | Literal `\"` |
|
|
13
|
+
| ----------- | ---- | ------------- | ------------ | ------------ |
|
|
14
|
+
| `message` | 56 | 28 | 111 | 146 |
|
|
15
|
+
| `reasoning` | 56 | 1 | 4 | 0 |
|
|
16
|
+
|
|
17
|
+
Half the replies were affected. This is a different defect from the em-dash corruption in
|
|
18
|
+
`2026-07-28-agent-reply-is-its-own-field.md`, and that fix still holds — em-dashes were clean across
|
|
19
|
+
all 56 replies here. This one is on the field that fix created.
|
|
20
|
+
|
|
21
|
+
**The model is choosing the wrong characters, not the wrong encoding.** Three replies carried one
|
|
22
|
+
paragraph break written as real newlines and another written as the two characters, inside the
|
|
23
|
+
_same string_. No serialization layer produces that — a layer that escapes, escapes everything. So
|
|
24
|
+
there is nothing to wait for on the provider side: the output is valid JSON, it satisfies the
|
|
25
|
+
response schema, and no parser can tell the difference.
|
|
26
|
+
|
|
27
|
+
**It also feeds back.** Assistant text is replayed verbatim into the next turn as trusted context,
|
|
28
|
+
so a damaged reply becomes evidence to the model of how this agent writes.
|
|
29
|
+
|
|
30
|
+
The runtime now repairs it deterministically at the single point where model output is validated —
|
|
31
|
+
upstream of persist, stream, render, and replay. `\n`, `\r\n`, `\t`, and `\"` are converted to the
|
|
32
|
+
characters they denote in `message`, in `reasoning`, and in session-memory values.
|
|
33
|
+
|
|
34
|
+
## Applies to
|
|
35
|
+
|
|
36
|
+
- **Every agent, not only session-capable ones.** `reasoning` and `memoryOps` are normalized on all
|
|
37
|
+
agents; `message` on the ones that have it.
|
|
38
|
+
- **Session memory especially.** A stored memory value is re-sent on every iteration of every
|
|
39
|
+
later turn and never ages out, so one literal escape stored there is one the model re-reads for
|
|
40
|
+
the life of the session. This surface had never been measured before this train.
|
|
41
|
+
- **No agent definition changes are required.** You do not edit your agents. The repair lives in the
|
|
42
|
+
runtime your bundle carries.
|
|
43
|
+
|
|
44
|
+
## Two behaviors worth knowing
|
|
45
|
+
|
|
46
|
+
1. **The normalizer is not markdown-aware.** A fenced code block or inline span containing a literal
|
|
47
|
+
`\n` **is** converted. This was a deliberate trade: a reply whose line breaks are all literal has
|
|
48
|
+
no line structure for a fence to start on, so the replies most in need of repair are exactly the
|
|
49
|
+
ones a fence scan would fail to parse. If you run an agent that discusses escape sequences or
|
|
50
|
+
regexes in its user-facing prose, this is the case to check.
|
|
51
|
+
2. **A doubled backslash is left alone**, and JSON-shaped memory values are skipped entirely — so a
|
|
52
|
+
stored JSON blob keeps its internal escaping and still parses.
|
|
53
|
+
|
|
54
|
+
## Required actions
|
|
55
|
+
|
|
56
|
+
1. **Take the `@elevasis/sdk` baseline bump** this train propagates, then reinstall in `operations/`
|
|
57
|
+
so the new worker bundle is present.
|
|
58
|
+
2. **Redeploy your operations bundle.** This is the step that closes the defect. The agent loop is
|
|
59
|
+
inlined into your deployed bundle, so an existing deployment keeps emitting damaged prose until
|
|
60
|
+
it is redeployed:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm -C operations exec elevasis-sdk deploy --prod
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A platform-side deploy does not fix this for you, and neither does the reinstall on its own.
|
|
67
|
+
|
|
68
|
+
3. **Do not treat existing transcripts as clean.** No backfill is performed. Replies and memory
|
|
69
|
+
snapshots written before your redeploy keep the literal escapes they already have. Memory
|
|
70
|
+
snapshots in particular are live agent state, and rewriting them mid-session was judged riskier
|
|
71
|
+
than the damage.
|
|
72
|
+
|
|
73
|
+
## Verification
|
|
74
|
+
|
|
75
|
+
- Run a multi-turn session and ask for a reply with paragraph structure — a short summary in two or
|
|
76
|
+
three paragraphs is enough. Read the reply: the signature failure is a visible `\n\n` between
|
|
77
|
+
sentences, or a quotation mark rendered as `\"`.
|
|
78
|
+
- **Check a reply containing quoted speech.** Quote escapes were the larger surface (146 of 257
|
|
79
|
+
measured occurrences), and one measured reply carried six of them with no newline escapes at all —
|
|
80
|
+
so a reply can be damaged purely on quotes and look fine structurally.
|
|
81
|
+
- If your agent declares `memoryPreferences`, run enough turns for it to write session memory, then
|
|
82
|
+
confirm a stored value comes back with real line breaks rather than literal ones.
|
|
83
|
+
|
|
84
|
+
## Not handled by /git-sync
|
|
85
|
+
|
|
86
|
+
- **The redeploy.** `/git-sync` commits and pushes the propagated dependency baseline. Your deployed
|
|
87
|
+
agents keep producing damaged prose until you run action 2 above.
|
|
88
|
+
- **Repairing existing transcripts or memory snapshots.** No backfill is performed, by decision.
|
package/reference/claude-config/sync-notes/2026-08-03-cli-gateway-errors-and-request-timeout.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Your CLI now explains a 502 instead of just reporting one, and no request hangs forever
|
|
2
|
+
|
|
3
|
+
## Why this note exists
|
|
4
|
+
|
|
5
|
+
This train publishes one package — `@elevasis/sdk` — and every change in it is in the CLI you type
|
|
6
|
+
commands into. Nothing about your workflows, organization model, or UI moves. But the CLI's failure
|
|
7
|
+
messages change, and one of them changes in a way that is meant to stop you doing something harmful.
|
|
8
|
+
|
|
9
|
+
**1. A gateway failure now tells you the work may still be running, and how to check.** Until now
|
|
10
|
+
every non-2xx response produced the same shape: `API request failed (502)`. That message is
|
|
11
|
+
technically accurate and practically dangerous, because the most natural reaction to it is to run the
|
|
12
|
+
command again.
|
|
13
|
+
|
|
14
|
+
A 502, 503, or 504 does not come from the platform API. It comes from a hop in front of it that ended
|
|
15
|
+
your connection while the API was still working. The request you sent may have completed in full. We
|
|
16
|
+
found this the hard way: a session turn returned 502 to the CLI after 300 seconds, and a later
|
|
17
|
+
database query showed the turn had **completed successfully server-side in 319.8 seconds** with a
|
|
18
|
+
model reply written and no error of any kind. The only thing that failed was the connection carrying
|
|
19
|
+
the answer back. It was turn 14 of a 14-turn run — re-running blind would have executed that turn a
|
|
20
|
+
second time.
|
|
21
|
+
|
|
22
|
+
The new message says the proxy ended the connection, that this is not the API rejecting your request,
|
|
23
|
+
that the work may still be running, and which command to use to look for a result before re-running.
|
|
24
|
+
|
|
25
|
+
**2. Every CLI request is now bounded by a timeout, defaulting to 2 hours.** Before, the CLI used a
|
|
26
|
+
bare `fetch()` with no signal, so a connection that stalled would sit there indefinitely with no
|
|
27
|
+
output. The limit is deliberately set high — it matches the server's own socket budget on long routes.
|
|
28
|
+
The goal is to bound an indefinite hang, not to police how long your work may take. Anything shorter
|
|
29
|
+
would make the CLI give up on executions the API is still legitimately serving, turning a working long
|
|
30
|
+
run into a reported failure. Override it with `ELEVASIS_CLI_TIMEOUT_MS` if you have a reason to.
|
|
31
|
+
|
|
32
|
+
**3. "Timed out" and "never reached the API" are now different messages.** A bare `fetch()` rejection
|
|
33
|
+
carries no status, so a dead port, a wrong API URL, and a stalled connection were indistinguishable.
|
|
34
|
+
They now read differently.
|
|
35
|
+
|
|
36
|
+
**4. The friendly authentication message now applies to every verb.** A 401 on `GET` printed a clear
|
|
37
|
+
"check your platform key" message; a 401 on `POST`, `PATCH`, or `DELETE` printed the raw response
|
|
38
|
+
body. All four now share the same message.
|
|
39
|
+
|
|
40
|
+
**5. Three dead exports were removed from the agent memory surface.** `MEMORY_DOMAINS.ACTION_OWNED`
|
|
41
|
+
was an empty array, `isActionOwnedKey` returned `false` for every input, and the `ActionOwnedKey` type
|
|
42
|
+
resolved to `never`. They are gone. The live half of that layer is unchanged and still enforced: an
|
|
43
|
+
agent still cannot write to a tool-owned memory key. If your project imports any of the three removed
|
|
44
|
+
names, it was importing something that could never do anything — but the import will now fail, so it
|
|
45
|
+
is worth a grep.
|
|
46
|
+
|
|
47
|
+
## Applies to
|
|
48
|
+
|
|
49
|
+
- **Every project that uses the `elevasis-sdk` CLI**, which is every template-family project. Items 1
|
|
50
|
+
through 4 arrive with the baseline bump and need no source edit from you.
|
|
51
|
+
- **Any script or CI job that parses CLI stderr.** The gateway, timeout, and transport messages are
|
|
52
|
+
new text. If something greps for `API request failed`, check it.
|
|
53
|
+
- **Any project importing `ACTION_OWNED`, `isActionOwnedKey`, or `ActionOwnedKey`** from the SDK, for
|
|
54
|
+
item 5. We found no such import in any template-family project, but only you can see out-of-tree
|
|
55
|
+
code.
|
|
56
|
+
- **Not applicable to your organization model, knowledge nodes, workflows, or UI.** Nothing in this
|
|
57
|
+
train touches authored content or any rendered surface.
|
|
58
|
+
|
|
59
|
+
## Required actions
|
|
60
|
+
|
|
61
|
+
1. **Take the `@elevasis/sdk` baseline bump** this train propagates, then reinstall in `operations/`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm -C operations install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. **Grep for the three removed memory exports** before you deploy. This is the only change in the
|
|
68
|
+
train that can break a build, and it is cheap to rule out:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
grep -rn "ACTION_OWNED\|isActionOwnedKey\|ActionOwnedKey" operations/src core/config
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
No output means item 5 does not affect you.
|
|
75
|
+
|
|
76
|
+
3. **Redeploy `operations/`.** The baseline bump changes what your next bundle contains; it does not
|
|
77
|
+
change what is already deployed:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pnpm -C operations exec elevasis-sdk deploy
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
4. **Check any script that matches on CLI error text.** If you have automation that branches on
|
|
84
|
+
`API request failed`, it will no longer match a gateway failure.
|
|
85
|
+
|
|
86
|
+
## Verification
|
|
87
|
+
|
|
88
|
+
- **Read the installed bundle, not the version number.** A bumped pin and a green sync report are
|
|
89
|
+
claims about intent; the installed file is the only ground truth:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
grep -c "GATEWAY_STATUSES" operations/node_modules/@elevasis/sdk/dist/cli.cjs
|
|
93
|
+
grep -c "ACTION_OWNED" operations/node_modules/@elevasis/sdk/dist/worker/index.js
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The first must be non-zero and the second must be `0`. Either result the other way means the
|
|
97
|
+
install did not land, regardless of what `package.json` says.
|
|
98
|
+
|
|
99
|
+
- **Provoke a transport failure and read the message.** The cheapest honest check that the new
|
|
100
|
+
branches are live, because it needs no broken server:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ELEVASIS_API_URL=http://localhost:9 pnpm -C operations exec elevasis-sdk list
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You should get a message naming the endpoint and saying the request never reached the API — not a
|
|
107
|
+
bare `fetch failed`.
|
|
108
|
+
|
|
109
|
+
- **Confirm the timeout is configurable.** Setting `ELEVASIS_CLI_TIMEOUT_MS=1` on any command should
|
|
110
|
+
produce a timeout message that quotes the limit back to you.
|
|
111
|
+
|
|
112
|
+
## Not handled by /git-sync
|
|
113
|
+
|
|
114
|
+
- **The `operations/` reinstall.** `/git-sync` propagates and commits the dependency baseline. The
|
|
115
|
+
`node_modules` copy your CLI actually executes is not updated until you run `pnpm install` yourself,
|
|
116
|
+
and the CLI will keep printing the old messages until you do.
|
|
117
|
+
- **The `operations/` redeploy.** Bumping the `@elevasis/sdk` pin changes what your next bundle
|
|
118
|
+
contains. Your currently deployed workers keep running the old bundle until you deploy.
|
|
119
|
+
- **The grep for the removed memory exports.** Nothing can detect an out-of-tree import for you.
|
|
120
|
+
- **Updating scripts that match on CLI error text.** The sync cannot see your automation.
|
|
@@ -328,6 +328,8 @@ const myAgent: AgentDefinition = {
|
|
|
328
328
|
|
|
329
329
|
`contract.inputSchema` is required, same as a workflow. `contract.outputSchema` is what a **non-session** (single-shot) agent like the example above returns — there is no conversational reply to read a structured answer from otherwise. A `sessionCapable: true` agent typically omits `outputSchema` and speaks through its conversational `message` instead.
|
|
330
330
|
|
|
331
|
+
The framework normalizes literal escape sequences out of that conversational `message`, out of `reasoning`, and out of session-memory values before any of them are persisted, streamed, or replayed. A model sometimes writes the two characters backslash-n where a line break belongs; the result is valid JSON that satisfies the response schema, so nothing upstream can catch it, and the operator ends up reading those characters mid-sentence. Nothing is required of you — no agent definition field changes — but two consequences are worth knowing. An agent that legitimately wants to show an escape sequence in prose (explaining a regex, say) will have it converted, because the normalizer is not markdown-aware. And a memory value that is itself JSON is skipped entirely, so a stored JSON blob keeps its internal escaping intact.
|
|
332
|
+
|
|
331
333
|
---
|
|
332
334
|
|
|
333
335
|
## DeploymentSpec
|