@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 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: "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."
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: { type: "string" }
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, request) {
44121
- const system = getSystem(model, request.systemPath);
44128
+ function getSystemInterfaceReadinessMarker(model, request2) {
44129
+ const system = getSystem(model, request2.systemPath);
44122
44130
  if (system === void 0) return void 0;
44123
- if (request.systemPath === LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath && request.interfaceKey === LEAD_GEN_CRM_HANDOFF_INTERFACE.interfaceKey) {
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 (request.interfaceKey === "api" && system.apiInterface !== void 0) {
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, request) {
44197
+ function computeInterfaceReadiness(model, request2) {
44190
44198
  const issues = [];
44191
- const system = getSystem(model, request.systemPath);
44192
- const systemInterface = getSystemInterfaceReadinessMarker(model, request);
44193
- const readinessProfile = systemInterface === void 0 ? void 0 : profileForInterface(request.systemPath, request.interfaceKey, systemInterface.readinessProfile);
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 "${request.systemPath}" is missing.`,
44201
- { ref: request.systemPath }
44208
+ `System "${request2.systemPath}" is missing.`,
44209
+ { ref: request2.systemPath }
44202
44210
  );
44203
44211
  return {
44204
44212
  ready: false,
44205
- systemPath: request.systemPath,
44206
- interfaceKey: request.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 "${request.systemPath}" does not declare interface "${request.interfaceKey}".`,
44217
- { path: readinessMarkerPath(request) }
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: request.systemPath,
44222
- interfaceKey: request.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(request.systemPath, request.interfaceKey)}" lifecycle is "${systemInterface.lifecycle}".`,
44233
- { path: `${readinessMarkerPath(request)}.lifecycle` }
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(request.systemPath, request.interfaceKey);
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(request.systemPath, request.interfaceKey)}" uses custom readiness profile "${checkedReadinessProfile}" but declares no readinessContract. Built-in profiles: ${formatSupportedReadinessProfiles()}.`,
44245
- { path: `${readinessMarkerPath(request)}.readinessContract`, ref: checkedReadinessProfile }
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: request.systemPath,
44250
- interfaceKey: request.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(request.systemPath, request.interfaceKey)}" must scope at least one active resource.`,
44262
- { path: `${readinessMarkerPath(request)}.resourceIds` }
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, request);
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, request);
44277
+ requireLeadGenInterfaceReadiness(issues, index, resources, request2);
44270
44278
  } else if (checkedReadinessProfile === CRM_API_INTERFACE.readinessProfile) {
44271
- requireCrmInterfaceReadiness(issues, index, resources, request);
44279
+ requireCrmInterfaceReadiness(issues, index, resources, request2);
44272
44280
  } else if (checkedReadinessProfile === LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile) {
44273
- requireLeadGenInterfaceReadiness(issues, index, resources, request);
44274
- requireCrmInterfaceReadiness(issues, index, resources, { ...request, allowForeignOwner: true });
44275
- requireHandoffBridgeReadiness(issues, model, request);
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, request);
44285
+ requireContractReadiness(issues, index, resources, readinessContract, request2);
44278
44286
  }
44279
44287
  }
44280
44288
  return {
44281
44289
  ready: issues.length === 0,
44282
- systemPath: request.systemPath,
44283
- interfaceKey: request.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
- `${varName} environment variable is required.
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
- async function apiGet(endpoint, apiUrl = resolveApiUrl()) {
46171
- const response = await fetch(`${apiUrl}${endpoint}`, {
46172
- headers: { Authorization: `Bearer ${getApiKey(apiUrl)}` }
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
- const response = await fetch(`${apiUrl}${endpoint}`, {
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
- const response = await fetch(`${apiUrl}${endpoint}`, {
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
- const response = await fetch(`${apiUrl}${endpoint}`, {
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.1",
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: "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."
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: { type: "string" }
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
@@ -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: "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."
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: { type: "string" }
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 AgentIterationOutputSchema = z.object({
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
- var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
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 or ACTION_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) || isActionOwnedKey(key)) {
5437
+ if (isToolOwnedKey(key)) {
5362
5438
  const owner = getKeyOwner(key);
5363
5439
  logger.action(
5364
5440
  "memory-set-rejected",
@@ -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: "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."
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: { type: "string" }
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 AgentIterationOutputSchema = z.object({
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
- var REQUIRED_ITERATION_KEYS = Object.entries(AgentIterationOutputSchema.shape).filter(([, fieldSchema]) => !fieldSchema.isOptional()).map(([key]) => key);
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 or ACTION_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) || isActionOwnedKey(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.1",
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/eslint-config": "0.0.0",
63
- "@repo/typescript-config": "0.0.0"
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.
@@ -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