@sema-agent/client-core 0.103.0 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +16 -13
  3. package/dist/adapter/downstream/eventToSdkMessage.js +2 -0
  4. package/dist/adapter/downstream/terminalToSdkResult.d.ts +0 -1
  5. package/dist/adapter/downstream/terminalToSdkResult.js +3 -1
  6. package/dist/agentSession/engineAgentRegistryRead.js +3 -0
  7. package/dist/coreValuePorts.d.ts +3 -1
  8. package/dist/coreValuePorts.js +5 -0
  9. package/dist/deviceRebind.d.ts +17 -4
  10. package/dist/deviceRebind.js +43 -14
  11. package/dist/engineAgentAbsence.d.ts +1 -0
  12. package/dist/executionPlacementWords.d.ts +3 -0
  13. package/dist/executionPlacementWords.js +2 -0
  14. package/dist/generated/engineFactTables.d.ts +3 -2
  15. package/dist/generated/engineFactTables.js +17 -1
  16. package/dist/generated/engineNoticeTables.js +12 -0
  17. package/dist/hitl/automaticAnswerVerdict.js +47 -11
  18. package/dist/hitl/sessionPolicyDeliverable.js +31 -5
  19. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  20. package/dist/hitl/toolApprovalWire.js +162 -15
  21. package/dist/host.d.ts +0 -3
  22. package/dist/hostTurnSettings.js +3 -1
  23. package/dist/humanTurn.js +2 -2
  24. package/dist/index.d.ts +5 -2
  25. package/dist/index.js +1 -0
  26. package/dist/jsonFaithful.js +5 -3
  27. package/dist/mcpLiveness.d.ts +1 -1
  28. package/dist/mcpWireCaps.js +1 -1
  29. package/dist/printModelStatus.d.ts +1 -0
  30. package/dist/printModelStatus.js +6 -2
  31. package/dist/retryStatus.d.ts +8 -1
  32. package/dist/retryStatus.js +13 -4
  33. package/dist/ruleCompileVerdict.d.ts +1 -0
  34. package/dist/ruleCompileVerdict.js +3 -1
  35. package/dist/seatContract.d.ts +7 -7
  36. package/dist/seatContract.js +39 -10
  37. package/dist/sessionRowTag.d.ts +24 -0
  38. package/dist/sessionRowTag.js +77 -0
  39. package/dist/systemReminderTag.js +14 -4
  40. package/docs/INTEGRATION-CLIENTS.md +252 -11
  41. package/package.json +5 -5
@@ -6,6 +6,7 @@ import { PROBE_VERDICTS } from "./modelCapabilityProbe.js";
6
6
  import { guardedRead, ownDataField, ownDataRefusalKeyOf, ownSendableField, readOwnDataValue } from "./ownKey.js";
7
7
  import { MCP_LIVENESS_STATES } from "./mcpLiveness.js";
8
8
  import { THINKING_LATCH_EVENTS, THINKING_LATCH_ROW_WORDS } from "./thinkingLatchWords.js";
9
+ import { EXECUTION_PLACEMENT_ROLES } from "./executionPlacementWords.js";
9
10
  const bad = (reason, field) => field === undefined ? { reason } : { reason, field };
10
11
  const nested = (prefix, verdict) => {
11
12
  const path = verdict.field === undefined
@@ -122,7 +123,22 @@ function shapeVerdict(spec, v, path, disc) {
122
123
  return true;
123
124
  }
124
125
  const shapeRule = (label, spec) => ({ label, test: (v, path) => shapeVerdict(spec, v, path) });
125
- const armsRule = (label, disc, arms) => ({
126
+ function keyGroupsVerdict(groups, v, path) {
127
+ let seen;
128
+ for (const g of groups) {
129
+ const present = g.filter((k) => !isAbsent(own(v, k)));
130
+ if (present.length === 0)
131
+ continue;
132
+ const missing = g.find((k) => !present.includes(k));
133
+ if (missing !== undefined)
134
+ return bad("missing key", pathAt(path, missing));
135
+ if (seen !== undefined)
136
+ return bad(`bad value (${seen.join("+")} | ${g.join("+")})`, pathAt(path, g[0]));
137
+ seen = g;
138
+ }
139
+ return true;
140
+ }
141
+ const armsRule = (label, disc, arms, groups = {}) => ({
126
142
  label,
127
143
  test: (v, path) => {
128
144
  if (!isPlainRecord(v))
@@ -130,7 +146,10 @@ const armsRule = (label, disc, arms) => ({
130
146
  const d = own(v, disc);
131
147
  if (typeof d !== "string" || !Object.hasOwn(arms, d))
132
148
  return bad("unknown arm", pathAt(path, disc));
133
- return shapeVerdict(arms[d], v, path, disc);
149
+ const verdict = shapeVerdict(arms[d], v, path, disc);
150
+ if (verdict !== true || !Object.hasOwn(groups, d))
151
+ return verdict;
152
+ return keyGroupsVerdict(groups[d], v, path);
134
153
  },
135
154
  });
136
155
  const isArraySlotKey = (k, n) => k === "length" || (/^(0|[1-9][0-9]*)$/.test(k) && Number(k) < n);
@@ -559,7 +578,7 @@ export const SEAT_TURN_SETTINGS_ORIGINS = Object.freeze({
559
578
  });
560
579
  export const SEAT_REWIND_INTENT_KEYS = ["mode", "targetMessageUuid"];
561
580
  export const ATTACHMENT_UPLOAD_REQUEST_KEYS = ["name", "mimeType", "bytes"];
562
- export const SESSION_ROW_PATCH_REQUEST_KEYS = Object.freeze(["sessionId", "title", "pinned"]);
581
+ export const SESSION_ROW_PATCH_REQUEST_KEYS = Object.freeze(["sessionId", "title", "pinned", "expectedRowTag"]);
563
582
  const SET_THINKING_LATCH_REQUEST_KEYS = Object.freeze(["sessionId", "event"]);
564
583
  export const SESSION_SEARCH_RESULT_KEYS = ["sessionId", "title", "matches"];
565
584
  export const GET_TRANSCRIPT_RESULT_KEYS = ["sessionId", "messages"];
@@ -582,7 +601,6 @@ const DEVICE_ROSTER_ROW = shapeRule("device row", {
582
601
  const DEVICE_BINDING = shapeRule("device binding", {
583
602
  deviceId: reqF(R_NONEMPTY),
584
603
  rev: reqF(R_INT0),
585
- boundAt: optF(R_STR),
586
604
  });
587
605
  const checkDeviceListOutcome = seatShapeCheck(armsRule("listDevices outcome", "kind", {
588
606
  ok: { devices: reqF(arrayRule(DEVICE_ROSTER_ROW)), dropped: reqF(R_INT0) },
@@ -594,23 +612,24 @@ const checkDeviceListOutcome = seatShapeCheck(armsRule("listDevices outcome", "k
594
612
  }));
595
613
  const checkDeviceSessionBindingOutcome = seatShapeCheck(armsRule("readSessionBinding outcome", "kind", {
596
614
  ok: { rootSessionId: reqF(R_NONEMPTY), boundDevice: reqF(DEVICE_BINDING) },
615
+ unbound: { role: reqF(oneOfRule("placement role", EXECUTION_PLACEMENT_ROLES)) },
597
616
  "not-found": {},
617
+ unsupported: { reason: reqF(oneOfRule("unsupported reason", ["route", "capability"])) },
598
618
  unreadable: DEVICE_UNREADABLE_SPEC,
599
- "lane-absent": DEVICE_LANE_ABSENT_SPEC,
600
619
  refused: DEVICE_REFUSED_SPEC,
601
620
  transport: DEVICE_TRANSPORT_SPEC,
602
621
  unarmed: {},
603
622
  }));
604
623
  const checkDeviceRebindOutcome = seatShapeCheck(armsRule("rebindSession outcome", "kind", {
605
624
  ok: { deviceId: reqF(R_NONEMPTY), rev: reqF(R_INT0), workspaceCarryover: optF(oneOfRule("carryover", ["none"])) },
606
- "active-run": { activeTaskId: optF(R_STR) },
625
+ "active-run": { activeTaskId: optF(R_STR), children: optF(arrayRule(R_STR)), retirePath: optF(R_NONEMPTY) },
607
626
  "rev-conflict": { currentRev: optF(R_INT0) },
608
627
  "not-found": {},
609
628
  unconfirmed: DEVICE_UNCONFIRMED_SPEC,
610
629
  "lane-absent": DEVICE_LANE_ABSENT_SPEC,
611
630
  refused: DEVICE_REFUSED_SPEC,
612
631
  unarmed: {},
613
- }));
632
+ }, { "active-run": [["activeTaskId"], ["children", "retirePath"]] }));
614
633
  const ENROLL_TOKEN_ALLOW = new Set(["token"]);
615
634
  const checkDeviceEnrollTokenOutcome = seatShapeCheck(armsRule("issueEnrollToken outcome", "kind", {
616
635
  ok: { token: reqF(R_NONEMPTY), expiresAt: optF(R_STR), ttlSec: optF(R_INT1) },
@@ -983,6 +1002,10 @@ const checkLocalSessionRecord = seatCheck((s) => {
983
1002
  if (!isAbsent(pinned) && typeof pinned !== "boolean") {
984
1003
  return bad("pinned must be a boolean when present (absent = not pinned or unknown)", "pinned");
985
1004
  }
1005
+ const rowTag = own(s, "rowTag");
1006
+ if (!isAbsent(rowTag) && (typeof rowTag !== "string" || rowTag.length === 0)) {
1007
+ return bad("rowTag must be a non-empty string when present (absent = this row cannot be written conditionally)", "rowTag");
1008
+ }
986
1009
  const latch = own(s, "thinkingLatch");
987
1010
  if (!isAbsent(latch)) {
988
1011
  const verdict = R_THINKING_LATCH_ROW.test(latch, "thinkingLatch");
@@ -1079,6 +1102,7 @@ const RETRY_COMMON_SPEC = {
1079
1102
  timeoutMs: optF(R_NUM),
1080
1103
  sleptMs: optF(R_NUM0),
1081
1104
  waitSegment: optF(R_NONEMPTY),
1105
+ seat: optF(R_NONEMPTY),
1082
1106
  };
1083
1107
  const RETRY_STATUS = armsRule("retry status", "kind", {
1084
1108
  stalled: { ...RETRY_COMMON_SPEC, detail: optF(R_STR) },
@@ -1451,8 +1475,12 @@ export const checkSessionRowPatchRequest = seatCheck((v) => {
1451
1475
  }
1452
1476
  if (!isAbsent(pinned) && typeof pinned !== "boolean")
1453
1477
  return bad("pinned must be a boolean when present", "pinned");
1478
+ const expectedRowTag = body.expectedRowTag;
1479
+ if (!isAbsent(expectedRowTag) && (typeof expectedRowTag !== "string" || expectedRowTag.length === 0)) {
1480
+ return bad("expectedRowTag must be the non-empty rowTag read from the same session row when present", "expectedRowTag");
1481
+ }
1454
1482
  if (isAbsent(title) && isAbsent(pinned))
1455
- return bad("nothing to change: carry at least one of title / pinned");
1483
+ return bad("nothing to change: carry at least one of title / pinned (expectedRowTag is a precondition, not a change)");
1456
1484
  return true;
1457
1485
  });
1458
1486
  export function isSessionRowPatchRequest(v) {
@@ -1744,6 +1772,7 @@ export const LOCAL_SESSION_RECORD_KEYS = [
1744
1772
  "queuedMessages",
1745
1773
  "parked",
1746
1774
  "pinned",
1775
+ "rowTag",
1747
1776
  "thinkingLatch",
1748
1777
  ];
1749
1778
  export const SEND_MESSAGE_REQUEST_KEYS = [
@@ -1789,7 +1818,7 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
1789
1818
  LocalSessionRecord: {
1790
1819
  guard: "isLocalSessionRecord",
1791
1820
  check: checkLocalSessionRecord,
1792
- validated: ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "thinkingLatch"],
1821
+ validated: ["sessionId", "engineSessionId", "model", "title", "cwd", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked", "pinned", "rowTag", "thinkingLatch"],
1793
1822
  unchecked: [],
1794
1823
  },
1795
1824
  ToolPermissionRequest: {
@@ -1910,7 +1939,7 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
1910
1939
  SessionRowPatchRequest: {
1911
1940
  guard: "isSessionRowPatchRequest",
1912
1941
  check: checkSessionRowPatchRequest,
1913
- validated: ["sessionId", "title", "pinned"],
1942
+ validated: ["sessionId", "title", "pinned", "expectedRowTag"],
1914
1943
  unchecked: [],
1915
1944
  },
1916
1945
  SetThinkingLatchRequest: {
@@ -0,0 +1,24 @@
1
+ export interface SessionRowPatchFields {
2
+ title?: string | null;
3
+ pinned?: boolean;
4
+ expectedRowTag?: string;
5
+ }
6
+ export interface SessionRowCurrentReading {
7
+ readonly rowTag?: string;
8
+ readonly title?: string;
9
+ readonly pinned?: boolean;
10
+ }
11
+ export type SessionRowPatchRefusal = {
12
+ readonly kind: 'already-applied';
13
+ readonly current: SessionRowCurrentReading;
14
+ } | {
15
+ readonly kind: 'row-changed';
16
+ readonly current?: SessionRowCurrentReading;
17
+ } | {
18
+ readonly kind: 'mirror';
19
+ readonly forkPath?: string;
20
+ } | {
21
+ readonly kind: 'tag-unsupported';
22
+ };
23
+ export declare function sessionRowTagOf(row: object | null | undefined): string | undefined;
24
+ export declare function sessionRowPatchRefusalOf(error: {} | null | undefined, sent: Readonly<SessionRowPatchFields> | null | undefined): SessionRowPatchRefusal | null;
@@ -0,0 +1,77 @@
1
+ import { guardedRead, readOwnDataValue } from './ownKey.js';
2
+ import { isSlotCarrier, readErrorRecord, readErrorString, readThrownSlot, slotReadOf } from './wireFailureShape.js';
3
+ const ROW_CHANGED = 'conflict.session_row_changed';
4
+ const SESSION_MIRROR = 'conflict.session_mirror';
5
+ const BODY_SHAPE = 'request.body_shape';
6
+ function ownData(v, key) {
7
+ if (v === null || typeof v !== 'object')
8
+ return undefined;
9
+ return guardedRead(() => readOwnDataValue(v, key));
10
+ }
11
+ export function sessionRowTagOf(row) {
12
+ const v = ownData(row, 'rowTag');
13
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
14
+ }
15
+ function currentRowOf(e) {
16
+ const row = guardedRead(() => readErrorRecord(e, 'currentRow'));
17
+ if (row === undefined)
18
+ return undefined;
19
+ const rowTag = sessionRowTagOf(row);
20
+ const title = ownData(row, 'title');
21
+ const pinned = ownData(row, 'pinned');
22
+ return Object.freeze({
23
+ ...(rowTag !== undefined ? { rowTag } : {}),
24
+ ...(typeof title === 'string' ? { title } : {}),
25
+ ...(typeof pinned === 'boolean' ? { pinned } : {}),
26
+ });
27
+ }
28
+ function pendingSlotOf(sent, key) {
29
+ if (sent === null || typeof sent !== 'object')
30
+ return { state: 'absent' };
31
+ const read = slotReadOf(sent, key);
32
+ if (read.presence === 'absent')
33
+ return { state: 'absent' };
34
+ if (read.presence !== 'present')
35
+ return { state: 'unreadable' };
36
+ return read.value === undefined ? { state: 'absent' } : { state: 'value', value: read.value };
37
+ }
38
+ function alreadyApplied(sent, current) {
39
+ const title = pendingSlotOf(sent, 'title');
40
+ const pinned = pendingSlotOf(sent, 'pinned');
41
+ if (title.state === 'unreadable' || pinned.state === 'unreadable')
42
+ return false;
43
+ let compared = 0;
44
+ if (title.state === 'value') {
45
+ if (typeof title.value !== 'string' || current.title !== title.value)
46
+ return false;
47
+ compared++;
48
+ }
49
+ if (pinned.state === 'value') {
50
+ if (typeof pinned.value !== 'boolean' || current.pinned !== pinned.value)
51
+ return false;
52
+ compared++;
53
+ }
54
+ return compared > 0;
55
+ }
56
+ export function sessionRowPatchRefusalOf(error, sent) {
57
+ if (!isSlotCarrier(error))
58
+ return null;
59
+ const status = readThrownSlot(error, 'status', 'data');
60
+ const code = readThrownSlot(error, 'errorCode', 'data');
61
+ if (status === 409 && code === ROW_CHANGED) {
62
+ const current = currentRowOf(error);
63
+ if (current !== undefined && alreadyApplied(sent, current))
64
+ return { kind: 'already-applied', current };
65
+ return { kind: 'row-changed', ...(current !== undefined ? { current } : {}) };
66
+ }
67
+ if (status === 409 && code === SESSION_MIRROR) {
68
+ const forkPath = guardedRead(() => readErrorString(error, 'forkPath'));
69
+ return { kind: 'mirror', ...(typeof forkPath === 'string' && forkPath.length > 0 ? { forkPath } : {}) };
70
+ }
71
+ if (status === 400 && code === BODY_SHAPE) {
72
+ const tag = ownData(sent, 'expectedRowTag');
73
+ if (typeof tag === 'string' && tag.length > 0)
74
+ return { kind: 'tag-unsupported' };
75
+ }
76
+ return null;
77
+ }
@@ -39,11 +39,21 @@ export function stripSystemReminderBlocks(text) {
39
39
  }
40
40
  return pos === 0 ? text : out + text.slice(pos);
41
41
  }
42
- const SYSTEM_REMINDER_TAG_TOKEN = /<[^A-Za-z0-9>]{0,12}system-reminder(?:[^>]{0,200}>)?/gi;
43
- const SYSTEM_REMINDER_TAG_HEAD = /<[^A-Za-z0-9>]{0,12}system-reminder/i;
42
+ const ENGINE_DEFUSAL_ENTITY = /&(lt|gt);/g;
43
+ const ENVELOPE_TAG_TOKEN = /<[^A-Za-z0-9>]{0,12}(?:system-reminder|task-notification)(?:[^>]{0,200}>)?/gi;
44
+ const CUT_TAG_HEAD = /<[^A-Za-z0-9>]{0,12}([A-Za-z-]{1,16})…/g;
45
+ const ENVELOPE_TAG_HEAD_BRACKET = /<(?=[^A-Za-z0-9>]{0,12}(?:system-reminder|task-notification|([A-Za-z-]{1,16})…))/gi;
46
+ const ENVELOPE_TAG_NAMES = Object.freeze(['system-reminder', 'task-notification']);
47
+ function isCutTagName(head) {
48
+ if (head === undefined || head === '')
49
+ return false;
50
+ const h = head.toLowerCase();
51
+ return ENVELOPE_TAG_NAMES.some((name) => name.length > h.length && name.startsWith(h));
52
+ }
44
53
  export function stripSystemReminderTagTokens(text) {
45
54
  if (typeof text !== 'string')
46
55
  return text;
47
- const once = text.replace(SYSTEM_REMINDER_TAG_TOKEN, ' ');
48
- return SYSTEM_REMINDER_TAG_HEAD.test(once) ? once.replace(/</g, '&lt;') : once;
56
+ const restored = text.replace(ENGINE_DEFUSAL_ENTITY, (_entity, name) => (name === 'lt' ? '<' : '>'));
57
+ const once = restored.replace(ENVELOPE_TAG_TOKEN, ' ').replace(CUT_TAG_HEAD, (head, name) => (isCutTagName(name) ? ' …' : head));
58
+ return once.replace(ENVELOPE_TAG_HEAD_BRACKET, (bracket, cutName) => (cutName === undefined || isCutTagName(cutName) ? ' ' : bracket));
49
59
  }