@xema/omni-protocol 0.1.94 → 0.1.95

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/index.d.ts CHANGED
@@ -1217,9 +1217,9 @@ export type DialResult = {
1217
1217
  status: "failed";
1218
1218
  failure: ProtocolFailure;
1219
1219
  };
1220
- export type BreakApproval = "not-requested"
1220
+ export type BreakStatus = "not-requested"
1221
1221
  /** Somebody has to decide. The agent is waiting on a person. */
1222
- | "awaiting-decision"
1222
+ | "awaiting-approval"
1223
1223
  /** Granted, and a promise to honour a later commit. */
1224
1224
  | "granted"
1225
1225
  /** Granted and begins when the current task ends. Nobody needs to act. */
@@ -1249,28 +1249,19 @@ export interface BreakReason {
1249
1249
  alwaysAvailable?: true;
1250
1250
  }
1251
1251
  export interface BreakRequest {
1252
+ /** Optional explanatory text. Omit when no explanation is needed; separate from reasonId. */
1252
1253
  reason?: string;
1253
1254
  /** The chosen `BreakReason.id`, where the provider publishes codes. */
1254
1255
  reasonId?: string;
1255
1256
  }
1256
- /**
1257
- * A break forced on the agent rather than requested by them.
1258
- *
1259
- * `by` is required in both arms. Who put somebody off the floor survives whether or not the
1260
- * break ends on a clock -- a forced break with no origin is a state the agent cannot reason
1261
- * about, and one that ends on a condition is still somebody's decision.
1262
- */
1257
+ /** A break forced on the agent; the agent must explicitly resume when ready. */
1263
1258
  export type ForcedBreak = {
1264
1259
  by: UserId;
1265
- endsAutomatically: true;
1266
- endsAt: IsoTimestamp;
1267
- } | {
1268
- by: UserId;
1269
- endsAutomatically: false;
1270
- endsAt?: never;
1260
+ /** Optional expected time on break, in milliseconds from actual start. Advisory only. */
1261
+ expectedDurationMs?: number;
1271
1262
  };
1272
1263
  export interface BreakState {
1273
- approval: BreakApproval;
1264
+ status: BreakStatus;
1274
1265
  /** Whether the agent may ask at all. Distinct from the fate of a request already made. */
1275
1266
  canRequestBreak: boolean;
1276
1267
  /** Shown when `canRequestBreak` is false, such as "Busy hours". */
@@ -1321,7 +1312,7 @@ export interface TeamMember {
1321
1312
  /** Omitted rather than invented: Omni renders it as a duration. */
1322
1313
  since?: IsoTimestamp;
1323
1314
  /** A request in flight or a grant not yet in effect. `not-requested` is absence, and `on-break` is `availability: "on-break"`. */
1324
- break?: Extract<BreakApproval, "awaiting-decision" | "granted" | "starting-after-task">;
1315
+ break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
1325
1316
  }
1326
1317
  /** A member asking this lead to join their call. */
1327
1318
  export interface LeadRequest {
@@ -1398,13 +1389,16 @@ export type TeamBreakCommand = {
1398
1389
  type: "set-break-policy";
1399
1390
  policy: "approval-required" | "automatically-approved" | "requests-suspended";
1400
1391
  }
1401
- /** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's forced break carries it as `activeReasonId`. */
1392
+ /** `reason` text is optional. `reasonId` selects a published `BreakReason.id`, required when reasons are published; the forced break carries it as `activeReasonId`. */
1402
1393
  | {
1403
1394
  type: "force-break";
1404
1395
  memberId: UserId;
1405
1396
  reasonId?: string;
1406
1397
  reason?: string;
1407
- } | {
1398
+ expectedDurationMs?: number;
1399
+ }
1400
+ /** Clears forced metadata only; preserves the committed break until the agent explicitly resumes. */
1401
+ | {
1408
1402
  type: "end-forced-break";
1409
1403
  memberId: UserId;
1410
1404
  };
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type TaskCompletion, type BreakApproval, type BrowserSessionKeyInput, type Channel, type ConnectContext, type Host, type HostGuarantees, type HostMute, type LoginStore, type HostReport, type Manifest, type ProviderEvent } from "./index.js";
1
+ import { type Adapter, type AuthenticationState, type ProviderEventEnvelope, type Snapshot, type Task, type TaskCompletion, type BreakStatus, type BrowserSessionKeyInput, type Channel, type ConnectContext, type Host, type HostGuarantees, type HostMute, type LoginStore, type HostReport, type Manifest, type ProviderEvent } from "./index.js";
2
2
  import { type ProtocolViolation } from "./validation.js";
3
3
  export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation } from "./validation.js";
4
4
  /**
@@ -135,11 +135,11 @@ export declare function assertReconnectWithMissedAssignments<C extends Channel>(
135
135
  * pending request nobody is coming to decide is worse than none. So the scenario is a request
136
136
  * that goes back to not-requested, and a later one that is granted.
137
137
  *
138
- * A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
138
+ * A request shows as `awaiting-approval` where a person decides, and as `granted` at once where
139
139
  * the provider decides alone; either is the request being made, and the rule is the same for
140
140
  * both.
141
141
  */
142
- export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakApproval[]): void;
142
+ export declare function assertDeniedAndRetriedBreak(approvals: readonly BreakStatus[]): void;
143
143
  /**
144
144
  * The event kinds a snapshot restates, and so supersedes when delivered while it is read. Every
145
145
  * other kind reports a transaction no snapshot carries, and a host applies it after the snapshot.
@@ -242,7 +242,7 @@ export interface BreakCandidate {
242
242
  export declare function assertBreakAttemptProviders(candidates: readonly BreakCandidate[], asked: readonly string[]): void;
243
243
  /** One published moment of a break asked for on a task: the approval, and how many tasks were outstanding. */
244
244
  export interface BreakOnTaskStep {
245
- approval: BreakApproval;
245
+ approval: BreakStatus;
246
246
  outstanding: number;
247
247
  }
248
248
  /**
package/dist/testing.js CHANGED
@@ -938,12 +938,12 @@ export function assertReconnectWithMissedAssignments(before, reconnect, missedTa
938
938
  * pending request nobody is coming to decide is worse than none. So the scenario is a request
939
939
  * that goes back to not-requested, and a later one that is granted.
940
940
  *
941
- * A request shows as `awaiting-decision` where a person decides, and as `granted` at once where
941
+ * A request shows as `awaiting-approval` where a person decides, and as `granted` at once where
942
942
  * the provider decides alone; either is the request being made, and the rule is the same for
943
943
  * both.
944
944
  */
945
945
  export function assertDeniedAndRetriedBreak(approvals) {
946
- const asked = approvals.findIndex(approval => approval === "awaiting-decision" || approval === "granted");
946
+ const asked = approvals.findIndex(approval => approval === "awaiting-approval" || approval === "granted");
947
947
  if (asked < 0)
948
948
  throw new Error("Break retry scenario requires an initial request");
949
949
  const refused = approvals.indexOf("not-requested", asked + 1);
@@ -1865,9 +1865,10 @@ async function driveOneCall(drive) {
1865
1865
  nothingLate();
1866
1866
  return found;
1867
1867
  }
1868
- // A request goes not-requested -> awaiting-decision | granted; a commit goes granted ->
1868
+ // A request goes not-requested -> awaiting-approval | granted; a commit goes granted ->
1869
1869
  // starting-after-task | on-break; work ending goes starting-after-task -> on-break; a denial,
1870
- // a cancel, an agent end or a lead end goes back to not-requested; a forced break arrives on-break
1870
+ // a cancel or an explicit agent end goes back to not-requested; a lead lifting forced status
1871
+ // preserves the committed break. A forced break arrives on-break
1871
1872
  // with `forced`. Nothing else is a move the guide describes.
1872
1873
  /** What a stream has said about the agent's break, and the moves it may not make. */
1873
1874
  export class BreakStream {
@@ -1994,7 +1995,7 @@ export function assertBreakAttemptProviders(candidates, asked) {
1994
1995
  * outstanding -- never beside a task, and never later than the step that has none.
1995
1996
  */
1996
1997
  export function assertBreakBeginsAfterTask(steps) {
1997
- const asked = steps.findIndex(step => step.approval === "awaiting-decision" || step.approval === "granted");
1998
+ const asked = steps.findIndex(step => step.approval === "awaiting-approval" || step.approval === "granted");
1998
1999
  if (asked < 0)
1999
2000
  throw new Error("Break-on-task scenario requires a request");
2000
2001
  if ((steps[asked]?.outstanding ?? 0) < 1)
@@ -52,7 +52,7 @@ const AUTHENTICATION_STATUSES = membersOf({
52
52
  "signed-out": true, authenticating: true, authenticated: true, refreshing: true, expired: true,
53
53
  });
54
54
  const BREAK_APPROVALS = membersOf({
55
- "not-requested": true, "awaiting-decision": true, granted: true, "starting-after-task": true, "on-break": true,
55
+ "not-requested": true, "awaiting-approval": true, granted: true, "starting-after-task": true, "on-break": true,
56
56
  });
57
57
  const TEAM_AVAILABILITIES = membersOf({
58
58
  ready: true, "on-task": true, "on-break": true, reserved: true, "signed-out": true,
@@ -80,7 +80,7 @@ const SNAPSHOT_REASONS = membersOf({
80
80
  });
81
81
  const SESSION_CAPABILITIES = membersOf({ breaks: true, team: true, preferences: true });
82
82
  const MEMBER_BREAKS = membersOf({
83
- "awaiting-decision": true, granted: true, "starting-after-task": true,
83
+ "awaiting-approval": true, granted: true, "starting-after-task": true,
84
84
  });
85
85
  const OFFERABLE_PHASES = membersOf({
86
86
  pending: true,
@@ -1298,19 +1298,12 @@ function validateForcedBreak(value, path, into) {
1298
1298
  into.add("break.forced.shape", path, "a forced break must be an object");
1299
1299
  return;
1300
1300
  }
1301
- // `by` is required either way. Who put somebody off the floor survives whether or not the
1302
- // break ends on a clock -- a forced break with no origin is a state the agent cannot
1303
- // reason about.
1304
1301
  into.require(isUserId(value.by), "break.forced.by", `${path}.by`, "a forced break must say who forced it");
1305
- if (value.endsAutomatically === true) {
1306
- into.timestamp(value.endsAt, "break.forced.endsAt", `${path}.endsAt`);
1307
- }
1308
- else if (value.endsAutomatically === false) {
1309
- into.require(value.endsAt === undefined, "break.forced.endsAt.unexpected", `${path}.endsAt`, "a break that does not end automatically must not carry an end time");
1310
- }
1311
- else {
1312
- into.add("break.forced.endsAutomatically", `${path}.endsAutomatically`, "a forced break must say whether it ends automatically");
1302
+ for (const field of ["endsAutomatically", "endsAt"]) {
1303
+ into.require(!Object.hasOwn(value, field), "break.forced.manualResume", `${path}.${field}`, "automatic ending fields are unsupported; the agent must resume manually");
1313
1304
  }
1305
+ if (value.expectedDurationMs !== undefined)
1306
+ into.require(typeof value.expectedDurationMs === "number" && Number.isFinite(value.expectedDurationMs) && value.expectedDurationMs > 0, "break.forced.expectedDurationMs", `${path}.expectedDurationMs`, "expectedDurationMs must be a positive finite number of milliseconds");
1314
1307
  }
1315
1308
  /** Checks break status against the full currently retained task set, after a transaction. */
1316
1309
  export function validateBreakStatus(state, tasks, path = "snapshot") {
@@ -1329,11 +1322,11 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
1329
1322
  // no task is a break waiting on work that does not exist, and an empty list reading as "still
1330
1323
  // finishing" is the plausible nought.
1331
1324
  ruleEvaluated("break.starting-after-task.tasks");
1332
- if (isPlainObject(state) && state.approval === "starting-after-task"
1325
+ if (isPlainObject(state) && state.status === "starting-after-task"
1333
1326
  && Array.isArray(tasks) && tasks.length === 0) {
1334
- into.add("break.starting-after-task.tasks", `${path}.break.approval`, "a break starting after the task waits on a task, and the snapshot carries none: with nothing outstanding the break is on-break");
1327
+ into.add("break.starting-after-task.tasks", `${path}.break.status`, "a break starting after the task waits on a task, and the snapshot carries none: with nothing outstanding the break is on-break");
1335
1328
  }
1336
- if (isPlainObject(state) && state.approval === "on-break"
1329
+ if (isPlainObject(state) && state.status === "on-break"
1337
1330
  && Array.isArray(tasks) && tasks.length > 0) {
1338
1331
  const listening = tasks.every((task) => isPlainObject(task) && task.listening !== undefined);
1339
1332
  if (!listening) {
@@ -1367,7 +1360,7 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1367
1360
  const command = request.command;
1368
1361
  const allowed = {
1369
1362
  "decide-break-request": ["type", "memberId", "decision", "reason"], "set-break-policy": ["type", "policy"],
1370
- "force-break": ["type", "memberId", "reasonId", "reason"], "end-forced-break": ["type", "memberId"],
1363
+ "force-break": ["type", "memberId", "reasonId", "reason", "expectedDurationMs"], "end-forced-break": ["type", "memberId"],
1371
1364
  };
1372
1365
  const fields = typeof command.type === "string" && Object.hasOwn(allowed, command.type) ? allowed[command.type] : undefined;
1373
1366
  if (!fields) {
@@ -1392,14 +1385,16 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1392
1385
  into.require(isPlainObject(member) && command.memberId !== self, "team.break.command.member", path, "the target must be another member of the current authorized team member list");
1393
1386
  if (command.type === "decide-break-request") {
1394
1387
  into.require(command.decision === "granted" || command.decision === "denied", "team.break.command.decision", path, "decide granted or denied");
1395
- into.require(isPlainObject(member) && member.break === "awaiting-decision", "team.break.command.awaiting", path, "decide only a currently awaiting-decision request");
1388
+ into.require(isPlainObject(member) && member.break === "awaiting-approval", "team.break.command.awaiting", path, "decide only a currently awaiting-approval request");
1396
1389
  }
1397
1390
  else {
1398
1391
  validateBreakState(context.memberBreak, `${path}.memberBreak`, into);
1399
1392
  const state = isPlainObject(context.memberBreak) ? context.memberBreak : {};
1400
1393
  if (command.type === "end-forced-break")
1401
- into.require(state.forced !== undefined && (state.approval === "on-break" || state.approval === "starting-after-task"), "team.break.command.endForcedBreak", path, "end a currently forced break");
1394
+ into.require(state.forced !== undefined && (state.status === "on-break" || state.status === "starting-after-task"), "team.break.command.endForcedBreak", path, "lift a current forced-break restriction without resuming the agent");
1402
1395
  if (command.type === "force-break") {
1396
+ if (command.expectedDurationMs !== undefined)
1397
+ into.require(typeof command.expectedDurationMs === "number" && Number.isFinite(command.expectedDurationMs) && command.expectedDurationMs > 0, "team.break.command.expectedDurationMs", `${path}.expectedDurationMs`, "expectedDurationMs must be positive finite milliseconds; it never resumes the agent");
1403
1398
  if (command.reasonId !== undefined)
1404
1399
  into.filled(command.reasonId, "team.break.command.reasonId", path, "reasonId must not be empty");
1405
1400
  const reasons = Array.isArray(state.reasons) ? state.reasons : [];
@@ -1431,9 +1426,9 @@ export function validateBreakCommand(method, request, state, context, path = "br
1431
1426
  }
1432
1427
  if (!isPlainObject(state) || into.violations.length)
1433
1428
  return into.violations;
1434
- const approval = state.approval;
1429
+ const status = state.status;
1435
1430
  if (method === "requestBreak") {
1436
- into.require(approval === "not-requested", "break.command.request.pending", path, "a new request starts only from not-requested");
1431
+ into.require(status === "not-requested", "break.command.request.pending", path, "a new request starts only from not-requested");
1437
1432
  if (!isPlainObject(request)) {
1438
1433
  into.add("break.request.shape", path, "requestBreak takes a request object");
1439
1434
  return into.violations;
@@ -1454,12 +1449,12 @@ export function validateBreakCommand(method, request, state, context, path = "br
1454
1449
  else {
1455
1450
  into.require(request === undefined, "break.command.arguments", path, "this break method takes no arguments");
1456
1451
  if (method === "commitBreak")
1457
- into.require(approval === "granted" || approval === "starting-after-task" || approval === "on-break", "break.command.commit.grant", path, "commit requires a grant; repeated committed state is idempotent");
1452
+ into.require(status === "granted" || status === "starting-after-task" || status === "on-break", "break.command.commit.grant", path, "commit requires a grant; repeated committed state is idempotent");
1458
1453
  if (method === "cancelBreak")
1459
- into.require(approval === "awaiting-decision" || approval === "granted", "break.command.cancel.precommit", path, "cancel only a pre-commit request; a raced commit requires commit recovery");
1454
+ into.require(status === "awaiting-approval" || status === "granted", "break.command.cancel.precommit", path, "cancel only a pre-commit request; a raced commit requires commit recovery");
1460
1455
  if (method === "endBreak") {
1461
- into.require(approval === "starting-after-task" || approval === "on-break", "break.command.end.started", path, "end a committed break, including a returning provider still finishing work");
1462
- into.require(state.forced === undefined, "break.command.end.forced", path, "an agent cannot end a forced break; an authorized lead ends it");
1456
+ into.require(status === "starting-after-task" || status === "on-break", "break.command.end.started", path, "end a committed break, including a returning provider still finishing work");
1457
+ // A forced break also ends only when the agent explicitly resumes.
1463
1458
  }
1464
1459
  }
1465
1460
  return into.violations;
@@ -1477,17 +1472,17 @@ export function validateBreakTransition(before, after, path = "break") {
1477
1472
  validateBreakState(after, `${path}.after`, into);
1478
1473
  if (into.violations.length || !isPlainObject(before) || !isPlainObject(after))
1479
1474
  return into.violations;
1480
- const from = before.approval;
1481
- const to = after.approval;
1475
+ const from = before.status;
1476
+ const to = after.status;
1482
1477
  const committed = to === "starting-after-task" || to === "on-break";
1483
- if (committed && (from === "not-requested" || from === "awaiting-decision") && after.forced === undefined) {
1484
- into.add("stream.breakState.commitBeforeGrant", `${path}.after.approval`, `${to} follows a commit, and a commit follows granted; the break stood at ${from}`);
1478
+ if (committed && (from === "not-requested" || from === "awaiting-approval") && after.forced === undefined) {
1479
+ into.add("stream.breakState.commitBeforeGrant", `${path}.after.status`, `${to} follows a commit, and a commit follows granted; the break stood at ${from}`);
1485
1480
  }
1486
- const backwards = (from === "on-break" && (to === "awaiting-decision" || to === "granted" || to === "starting-after-task")) ||
1487
- (from === "starting-after-task" && (to === "awaiting-decision" || to === "granted")) ||
1488
- (from === "granted" && to === "awaiting-decision");
1481
+ const backwards = (from === "on-break" && (to === "awaiting-approval" || to === "granted" || to === "starting-after-task")) ||
1482
+ (from === "starting-after-task" && (to === "awaiting-approval" || to === "granted")) ||
1483
+ (from === "granted" && to === "awaiting-approval");
1489
1484
  if (backwards)
1490
- into.add("stream.breakState.backwards", `${path}.after.approval`, `a break does not go from ${from} back to ${to}; a new request passes through not-requested`);
1485
+ into.add("stream.breakState.backwards", `${path}.after.status`, `a break does not go from ${from} back to ${to}; a new request passes through not-requested`);
1491
1486
  return into.violations;
1492
1487
  }
1493
1488
  function validateBreakState(value, path, into) {
@@ -1496,7 +1491,8 @@ function validateBreakState(value, path, into) {
1496
1491
  return;
1497
1492
  }
1498
1493
  into.require(!Object.hasOwn(value, "imposed"), "break.forced.renamed", `${path}.imposed`, "imposed was renamed to forced; use only forced");
1499
- into.oneOf(value.approval, BREAK_APPROVALS, "break.approval", `${path}.approval`);
1494
+ into.require(!Object.hasOwn(value, "approval"), "break.status.renamed", `${path}.approval`, "BreakState.approval was renamed to status; use only status");
1495
+ into.oneOf(value.status, BREAK_APPROVALS, "break.status", `${path}.status`);
1500
1496
  into.require(!Object.hasOwn(value, "mayAsk"), "break.canRequestBreak.renamed", `${path}.mayAsk`, "mayAsk was renamed to canRequestBreak; use only canRequestBreak");
1501
1497
  into.require(typeof value.canRequestBreak === "boolean", "break.canRequestBreak", `${path}.canRequestBreak`, "canRequestBreak says whether the agent may ask for a break: a boolean");
1502
1498
  into.require(!Object.hasOwn(value, "refusedReason"), "break.requestUnavailableReason.renamed", `${path}.refusedReason`, "refusedReason was renamed to requestUnavailableReason; use only requestUnavailableReason");
@@ -1517,14 +1513,14 @@ function validateBreakState(value, path, into) {
1517
1513
  into.filled(value.activeReasonId, "break.activeReasonId", `${path}.activeReasonId`, "activeReasonId must not be empty when present");
1518
1514
  // A break nobody is on has no reason. Reporting one beside `not-requested` describes a
1519
1515
  // break that is not happening.
1520
- into.require(value.approval !== "not-requested", "break.activeReasonId.approval", `${path}.activeReasonId`, "activeReasonId must be omitted when no break is requested or in effect");
1516
+ into.require(value.status !== "not-requested", "break.activeReasonId.status", `${path}.activeReasonId`, "activeReasonId must be omitted when no break is requested or in effect");
1521
1517
  }
1522
1518
  if (value.forced !== undefined) {
1523
1519
  validateForcedBreak(value.forced, `${path}.forced`, into);
1524
1520
  // A forced break is a break somebody forced; beside `not-requested` there is no break.
1525
1521
  // A forced break is a break in progress or about to be: it travels with on-break or
1526
1522
  // starting-after-task and nothing else. Beside granted, the host would commit a break nobody asked for.
1527
- into.require(value.approval === "on-break" || value.approval === "starting-after-task", "break.forced.approval", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.approval)} says the agent asked, or that there is none`);
1523
+ into.require(value.status === "on-break" || value.status === "starting-after-task", "break.forced.status", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.status)} says the agent asked, or that there is none`);
1528
1524
  }
1529
1525
  if (value.reasons === undefined)
1530
1526
  return;
@@ -1563,8 +1559,8 @@ function validateBreakState(value, path, into) {
1563
1559
  // forced one included, since the lead's force-break command named it. A break of no kind is a break whose
1564
1560
  // rules -- who may listen through it, whether it counts -- nobody can apply.
1565
1561
  ruleEvaluated("break.activeReasonId.required");
1566
- if (seen.size > 0 && (value.approval === "on-break" || value.approval === "starting-after-task")) {
1567
- into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.approval)} on a provider that publishes reasons names the one it is on`);
1562
+ if (seen.size > 0 && (value.status === "on-break" || value.status === "starting-after-task")) {
1563
+ into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.status)} on a provider that publishes reasons names the one it is on`);
1568
1564
  }
1569
1565
  }
1570
1566
  export function validateTeamMembers(teamMembers, path = "team", context = {}) {
package/guide.md CHANGED
@@ -43,9 +43,8 @@ are used precisely throughout and mean nothing looser here.
43
43
  | **Listen** | A lead listening to a member's call unasked, from the team member list: `listen` in silence, `coach` heard by the agent alone, `join-call` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Listening to a call**. |
44
44
  | **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
45
45
 
46
- Six words describe *what state a thing is in*, and they are not interchangeable: each belongs to
47
- one thing, so a bare "status" in conversation is always the authentication session's, and a
48
- transport, a task, a break and a call each have a word of their own:
46
+ These fields describe state within their containing objects. Both authentication and break
47
+ state use `status`; name the object when discussing them to avoid ambiguity.
49
48
 
50
49
  | Word | Belongs to | Values |
51
50
  | --- | --- | --- |
@@ -53,7 +52,7 @@ transport, a task, a break and a call each have a word of their own:
53
52
  | `media` | A task's audio | `started`, `ended` |
54
53
  | `transport` | A connection | `connecting`, `active`, `error` |
55
54
  | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
56
- | `approval` | A break request | `not-requested`, `awaiting-decision`, `granted`, `starting-after-task`, `on-break` |
55
+ | `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
57
56
  | `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
58
57
 
59
58
  ## Versioning
@@ -929,9 +928,9 @@ keeps a choice about it. See **The station is the host's**.
929
928
  ## Breaks
930
929
 
931
930
  ```ts
932
- type BreakApproval =
931
+ type BreakStatus =
933
932
  | "not-requested"
934
- | "awaiting-decision"
933
+ | "awaiting-approval"
935
934
  | "granted"
936
935
  | "starting-after-task"
937
936
  | "on-break";
@@ -949,12 +948,13 @@ type BreakRequest = {
949
948
  reasonId?: string;
950
949
  };
951
950
 
952
- type ForcedBreak =
953
- | { by: UserId; endsAutomatically: true; endsAt: IsoTimestamp }
954
- | { by: UserId; endsAutomatically: false; endsAt?: never };
951
+ type ForcedBreak = {
952
+ by: UserId;
953
+ expectedDurationMs?: number;
954
+ };
955
955
 
956
956
  type BreakState = {
957
- approval: BreakApproval;
957
+ status: BreakStatus;
958
958
  canRequestBreak: boolean;
959
959
  requestUnavailableReason?: string;
960
960
  decisionReason?: string;
@@ -990,7 +990,7 @@ type TeamMember = {
990
990
  id: UserId;
991
991
  availability: TeamMemberAvailability;
992
992
  since?: IsoTimestamp;
993
- break?: Extract<BreakApproval, "awaiting-decision" | "granted" | "starting-after-task">;
993
+ break?: Extract<BreakStatus, "awaiting-approval" | "granted" | "starting-after-task">;
994
994
  };
995
995
 
996
996
  type LeadRequest = {
@@ -1036,7 +1036,7 @@ type TeamLeadAssistCommand =
1036
1036
  type TeamBreakCommand =
1037
1037
  | { type: "decide-break-request"; memberId: UserId; decision: "granted" | "denied"; reason?: string }
1038
1038
  | { type: "set-break-policy"; policy: "approval-required" | "automatically-approved" | "requests-suspended" }
1039
- | { type: "force-break"; memberId: UserId; reasonId?: string; reason?: string }
1039
+ | { type: "force-break"; memberId: UserId; reasonId?: string; reason?: string; expectedDurationMs?: number }
1040
1040
  | { type: "end-forced-break"; memberId: UserId };
1041
1041
 
1042
1042
  type TeamBreakCommandRequest = {
@@ -1924,7 +1924,7 @@ them from what arrives later.
1924
1924
  | --- | --- |
1925
1925
  | `breaks` | This login may request a break. Requires the four break methods on the connection. |
1926
1926
  | `team` | This login leads a team. The provider publishes a `TeamMembers` object to it on every snapshot — `members: []` when nobody is in it — and to nobody else. |
1927
- | `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force-break, end-forced-break, decide-break-request, set-break-policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-decision`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
1927
+ | `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — force-break, end-forced-break, decide-break-request, set-break-policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-approval`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
1928
1928
  | `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
1929
1929
  | `team.listeningControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `listen`, `coach`, `join-call`. The list always includes `listen`, since the other two begin from one. Requires `executeTeamListen`. See **Listening to a call**. |
1930
1930
  | `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the team member list carries `policies`. |
@@ -2131,7 +2131,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
2131
2131
  | --- | --- |
2132
2132
  | `transport` | Current `TransportStatus` — whether this provider's transport can serve the login. Defined under **`transport-status`**. |
2133
2133
  | `loginId` | Identity of this login. It must match the connection context. |
2134
- | `break` | Complete break state, including approval, whether the agent may ask, reasons, retry details, and any forced break. |
2134
+ | `break` | Complete break state, including status, whether the agent may ask, reasons, retry details, and any forced break. |
2135
2135
  | `tasks` | Complete set of tasks currently offered to or owned by this agent. |
2136
2136
  | `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
2137
2137
  | `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
@@ -3607,8 +3607,8 @@ nothing while none are being accepted — so they are not published separately.
3607
3607
 
3608
3608
  | Field | Contract |
3609
3609
  | --- | --- |
3610
- | `approval` | Where the agent's current request stands. See the states below. |
3611
- | `canRequestBreak` | Whether the agent may ask at all. Distinct from `approval`. |
3610
+ | `status` | Where the agent’s break lifecycle stands. See the states below. |
3611
+ | `canRequestBreak` | Whether the agent may ask at all. Distinct from `status`. |
3612
3612
  | `requestUnavailableReason` | Display-ready reason shown when `canRequestBreak` is false — a standing gate that applies to everyone. |
3613
3613
  | `decisionReason` | The words whoever decided attached, from `decide.reason`. About one request and one decision, not a standing gate. |
3614
3614
  | `retryRequestAfterMs` | Milliseconds until the agent may retry a break request, when the provider can say. |
@@ -3619,10 +3619,10 @@ nothing while none are being accepted — so they are not published separately.
3619
3619
  A request can be waiting for two unrelated things, and they are separate values because
3620
3620
  rendering one as the other tells an agent to wait for somebody who is never coming:
3621
3621
 
3622
- | `approval` | Meaning |
3622
+ | `status` | Meaning |
3623
3623
  | --- | --- |
3624
3624
  | `not-requested` | No request outstanding. |
3625
- | `awaiting-decision` | A person has to decide. The agent is waiting on somebody. |
3625
+ | `awaiting-approval` | A person has to decide. The agent is waiting on somebody. |
3626
3626
  | `granted` | A person decided yes. Omni may now tell this provider to stop the agent; until it does, work continues normally, and this says nothing about why Omni has not. |
3627
3627
  | `starting-after-task` | Omni has told the provider to stop; the break begins when the current task ends. No new work arrives meanwhile, and nobody needs to act. It waits on a task, so beside no task it is refused (`break.starting-after-task.tasks`): a committed break with nothing outstanding is `on-break`. |
3628
3628
  | `on-break` | The agent is on the break now. It holds no task: a break begins when the work ends, so a snapshot reporting `on-break` beside a task is refused as `break.on-break.tasks`. The one exception is a lead's listening task during a `coaching`, `administrative` or `training` break -- see **Listening to a call**. |
@@ -3630,7 +3630,7 @@ rendering one as the other tells an agent to wait for somebody who is never comi
3630
3630
  A denial is a decision, not a standing approval state. The provider transitions the request directly
3631
3631
  to `not-requested`; Omni returns the agent to idle and never asks again on their behalf. They saw the
3632
3632
  answer and ask again when they want to. `decisionReason` may carry the words attached to that
3633
- decision, but `approval` does not remain denied.
3633
+ decision, but `status` does not remain denied.
3634
3634
 
3635
3635
  A provider reports `starting-after-task` only after Omni commits a `granted` request while
3636
3636
  work is still active. Omni does not send the request again, because asking again would not move
@@ -3642,6 +3642,18 @@ something a busy hour can cancel, and Omni keeps offering those while the rest a
3642
3642
 
3643
3643
  ### Forced breaks
3644
3644
 
3645
+ Migration: the former awaiting-decision value is now `awaiting-approval` in break status
3646
+ and the team member’s pending break state. The old value is rejected without an alias;
3647
+ the request can still be granted or denied, and ordering rules are unchanged.
3648
+
3649
+ Migration: the former BreakApproval type is now `BreakStatus`, matching `BreakState.status`.
3650
+ The former type is not exported as an alias; its values and behavior are unchanged.
3651
+
3652
+ Migration: `BreakState.status` replaces the former approval field. It covers the full break
3653
+ lifecycle. The old field is rejected even alongside status; related diagnostics now use status.
3654
+ The `BreakStatus` union and its values, `reasons`, `activeReasonId`, and the separate testing
3655
+ helper BreakOnTaskStep.approval are unchanged. Hosts and providers must update together.
3656
+
3645
3657
  Migration: the break-state field formerly named retryAfterMs is now `BreakState.retryRequestAfterMs`,
3646
3658
  with diagnostic `break.retryRequestAfterMs`. It remains an optional non-negative finite number
3647
3659
  of milliseconds; the former break field is rejected even alongside the new field.
@@ -3664,8 +3676,8 @@ mayAsk. Request eligibility remains distinct from approval; existing alwaysAvail
3664
3676
  exceptions are unchanged. Hosts and providers must update together.
3665
3677
 
3666
3678
  Migration: ImposedBreak is now `ForcedBreak`, and the former imposed field is now
3667
- `BreakState.forced`. Diagnostic and harness coverage names use `break.forced`; the agent-end
3668
- diagnostic is `break.command.end.forced`. Update hosts and providers together. The old field
3679
+ `BreakState.forced`. Diagnostic and harness coverage names use `break.forced`. The former agent-end prohibition
3680
+ has been removed: forced breaks also permit explicit agent resumption. Update hosts and providers together. The old field
3669
3681
  is rejected, including when both spellings are sent; no compatibility alias is provided.
3670
3682
  The break policy value formerly named auto-approve is now `automatically-approved`.
3671
3683
  Requests are approved automatically; approval does not skip commitment or the prerequisites
@@ -3684,11 +3696,11 @@ The team break command formerly named place is now `force-break`. Hosts and prov
3684
3696
  adopt the new command together; place and the interim force spelling are rejected without aliases.
3685
3697
  The team break command formerly named release is now `end-forced-break`, and its diagnostic is
3686
3698
  `team.break.command.endForcedBreak`. The former release command and the interim end spelling are rejected without aliases.
3687
- **End a forced break** is a lead action through `executeTeamBreak`; the agent’s `endBreak`
3688
- method still cannot end a forced break. Break ordering and permission rules are unchanged.
3699
+ The agent’s `endBreak` now applies to forced breaks as well as requested breaks.
3689
3700
 
3690
- `ForcedBreak` says who forced the break, whether automatic ending is enabled, and, when enabled,
3691
- when the provider will end it. A break the agent did not choose is not manually resumable by them.
3701
+ `ForcedBreak` names who forced the break and may include an advisory `expectedDurationMs`.
3702
+ The agent must explicitly resume when ready. The retired endsAutomatically and endsAt fields
3703
+ are rejected, including when an expected duration is also provided.
3692
3704
 
3693
3705
  **Every forced break has a person behind it.** A lead or a manager forced it; there is no such
3694
3706
  thing as a break the platform forced on its own. Where a platform applies one automatically, it is
@@ -3699,8 +3711,8 @@ Omni resolves the name to show with `describeUsers()`, so a provider sends the i
3699
3711
  a display name.
3700
3712
 
3701
3713
  **A forced break travels with `on-break` or `starting-after-task`, and nothing else.** It is a
3702
- break in progress or about to be; beside `granted` or `awaiting-decision` the host would read a
3703
- request the agent never made and commit it (`break.forced.approval`). Where the provider publishes
3714
+ break in progress or about to be; beside `granted` or `awaiting-approval` the host would read a
3715
+ request the agent never made and commit it (`break.forced.status`). Where the provider publishes
3704
3716
  `reasons`, the lead's `force-break` named one, and the member's state carries it as `activeReasonId`.
3705
3717
 
3706
3718
  For example:
@@ -3708,17 +3720,34 @@ For example:
3708
3720
  ```ts
3709
3721
  forced: {
3710
3722
  by: "manager-1042",
3711
- endsAutomatically: true,
3712
- endsAt: "2026-08-21T10:00:00.000Z"
3723
+ expectedDurationMs: 600000
3713
3724
  }
3714
3725
  ```
3715
3726
 
3716
- The presence of `forced` means the agent cannot end the break manually, so Omni withdraws its
3717
- Resume control from that agent. With `endsAutomatically: true`, the provider ends the break at
3718
- `endsAt`; with `endsAutomatically: false`, it does not end the break on a timer. An authorized lead
3719
- may end either form with **Resume**, not only whoever forced it. Omni shows **Stopped by <who>**,
3720
- resolving the name with `describeUsers()`, and shows when the break will end where automatic ending
3721
- is enabled.
3727
+ The host keeps **Resume** available for an agent on a forced break. The agent explicitly chooses
3728
+ Resume; the host sends `endBreak()` and follows the provider-confirmed state before resuming
3729
+ work. An expected duration is a positive finite number of milliseconds, measured from actual
3730
+ entry into `on-break`, excluding any `starting-after-task` wait. Omission means no expected
3731
+ duration was supplied. Neither duration expiry nor an overdue indication authorizes the host
3732
+ or provider to end the break, restore availability, or route work to the agent.
3733
+
3734
+ **A lead lifting the restriction does not resume the agent.** On an applied
3735
+ `end-forced-break`, the provider clears `BreakState.forced` while preserving the current
3736
+ `on-break` or `starting-after-task` status and `activeReasonId`. This command cannot publish
3737
+ `not-requested`, restore readiness, or start routing work. Ordinary progress from
3738
+ `starting-after-task` to `on-break` still happens when work finishes. The host follows the
3739
+ published state and waits for the agent's explicit Resume, even after reconnect. Historical
3740
+ attribution is not rewritten by clearing the current forced marker.
3741
+
3742
+ The agent may explicitly resume either before or after the lead lifts the restriction. A lead
3743
+ command, timer, late snapshot or reconnect is never a substitute for that choice. Providers
3744
+ must correlate resumption to the authenticated agent's action; a state-only ordering validator
3745
+ cannot establish the cause of a transition. The usual multi-provider end/reconciliation flow
3746
+ still applies, and work resumes only on confirmed provider state.
3747
+
3748
+ A host may display the expected duration. A countdown requires an evidenced actual start;
3749
+ a received snapshot, replay or reconnect is not a new start and cannot restart the duration.
3750
+ Without that evidence, show the duration without inventing a start or return time.
3722
3751
 
3723
3752
  A break applies to the **agent**, not to one provider. When a provider forces one, Omni immediately
3724
3753
  requests a break on every other connected provider, or they would keep routing work to somebody who
@@ -3804,8 +3833,8 @@ request object only to the request method, and undefined to the other three.
3804
3833
  | --- | --- |
3805
3834
  | `requestBreak` | `not-requested`; selected current reason code when codes exist; `canRequestBreak` or the selected reason's `alwaysAvailable` exception. Free text does not replace a code. |
3806
3835
  | `commitBreak` | `granted`, or already committed for an idempotent repeat. A later change to `canRequestBreak` does not revoke the grant. |
3807
- | `cancelBreak` | `awaiting-decision` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
3808
- | `endBreak` | `on-break` or `starting-after-task` during reconciliation; an agent cannot end a forced break. |
3836
+ | `cancelBreak` | `awaiting-approval` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
3837
+ | `endBreak` | `on-break` or `starting-after-task` during reconciliation; the agent may explicitly end a requested or forced break. |
3809
3838
 
3810
3839
  Use `validateTeamBreakCommand(request, context)` for lead decisions, forcing a break, ending a forced break and
3811
3840
  policy commands. It requires the live lead capability and active transport, a current target
@@ -3829,8 +3858,18 @@ required; per-provider validation does not implement the multi-provider coordina
3829
3858
 
3830
3859
  ### `requestBreak(request)`
3831
3860
 
3861
+ **Explanatory text is optional.** An agent may omit `BreakRequest.reason` for any break type.
3862
+ A lead may likewise omit reason text from `force-break`. Do not require a text explanation
3863
+ just because a break is agent-requested or forced. If supplied, text must be nonempty; omit
3864
+ it rather than sending an empty string. A selected `reasonId` identifies a published break
3865
+ choice and is separate from this optional text.
3866
+
3867
+ With published choices, `{ reasonId: "lunch" }` is a valid request without explanation;
3868
+ without published choices, `{}` is valid. A lead can similarly send a `force-break` command
3869
+ with its member ID and, where required, the selected reason ID, without reason text.
3870
+
3832
3871
  Requests permission to stop the agent later; it does not itself stop work. The provider continues
3833
- offering work and reports `awaiting-decision` or `granted` through `break-state` events. If the request is denied, the
3872
+ offering work and reports `awaiting-approval` or `granted` through `break-state` events. If the request is denied, the
3834
3873
  provider reports `not-requested` directly, with `decisionReason` when one was supplied.
3835
3874
 
3836
3875
  **An agent asks from anywhere — idle or on a task** — and Omni offers the request in the task
@@ -3959,7 +3998,7 @@ Omni coordinates one break attempt as follows:
3959
3998
  capacity until then. `refreshing` keeps a provider in — its identity and capabilities remain
3960
3999
  available and work continues. `assertBreakAttemptProviders` holds a host to this set.
3961
4000
  2. Enter `requesting-break`. Keep the agent's normal capacity in place throughout this phase.
3962
- 3. Send one `requestBreak` to every asked provider. A provider reports `awaiting-decision` or
4001
+ 3. Send one `requestBreak` to every asked provider. A provider reports `awaiting-approval` or
3963
4002
  `granted`; neither state stops work. A denial transitions directly to `not-requested` and
3964
4003
  causes Omni to take the cancel path.
3965
4004
  4. If every asked provider reports `granted`, durably choose commit, enter `committing-break`,
@@ -3974,7 +4013,7 @@ Omni coordinates one break attempt as follows:
3974
4013
  5. If any asked provider fails or denies the request, cannot be reconciled within the bounded
3975
4014
  decision timeout, or the agent cancels before commit, durably choose cancel and enter
3976
4015
  `cancelling-break`. Send `cancelBreak()` to every provider still reporting
3977
- `awaiting-decision` or `granted`. Work continues during cancellation because no stop was
4016
+ `awaiting-approval` or `granted`. Work continues during cancellation because no stop was
3978
4017
  committed. Return to `working` only after no provider retains either state.
3979
4018
 
3980
4019
  Commit and cancel are mutually exclusive decisions for one break attempt. Once Omni chooses commit
@@ -4044,11 +4083,11 @@ asked for, but only until the session ends — after a reload or reconnect, or w
4044
4083
  the agent on the break itself, the provider is the only one who knows.
4045
4084
 
4046
4085
  Omit it when you cannot say, and when there is no break: reporting a reason alongside
4047
- `approval: "not-requested"` describes a break that is not happening, and is rejected.
4086
+ `status: "not-requested"` describes a break that is not happening, and is rejected.
4048
4087
 
4049
4088
  ### `cancelBreak()`
4050
4089
 
4051
- Cancels the active pre-commit request while its approval is `awaiting-decision` or `granted`.
4090
+ Cancels the active pre-commit request while its status is `awaiting-approval` or `granted`.
4052
4091
  Cancellation releases the request but does not restore work because work never stopped. If
4053
4092
  commit already won, the provider returns `omni.break-already-committed`. The resulting state is
4054
4093
  reported through `break-state`.
@@ -4139,8 +4178,8 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
4139
4178
  | --- | --- |
4140
4179
  | `{ type: "decide-break-request", memberId: UserId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
4141
4180
  | `{ type: "set-break-policy", policy }` | `approval-required`, `automatically-approved`, or `requests-suspended`. |
4142
- | `{ type: "force-break", memberId: UserId, reasonId?, reason? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. |
4143
- | `{ type: "end-forced-break", memberId: UserId }` | Ends a forced break on that member, whoever forced it. |
4181
+ | `{ type: "force-break", memberId: UserId, reasonId?, reason?, expectedDurationMs? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. Optional `expectedDurationMs` is advisory and is published on the resulting forced break. |
4182
+ | `{ type: "end-forced-break", memberId: UserId }` | Lifts the forced-break restriction, whoever forced it, by clearing `BreakState.forced`. The committed break continues; only the agent resumes work. |
4144
4183
 
4145
4184
  `memberId` is this provider's own identifier for the member, as published on its team member list. It is
4146
4185
  never an identifier from another provider, and Omni does not translate between them; names come
@@ -4194,7 +4233,7 @@ they joined -- is answered `failed`, whatever their ceiling; the request stands
4194
4233
  or until it is withdrawn or declined.
4195
4234
 
4196
4235
  **A lead on a break does not join.** A break is a reported state in which the agent is not working,
4197
- and a join is work. Omni offers Join to a lead only while their own `BreakState.approval` is neither
4236
+ and a join is work. Omni offers Join to a lead only while their own `BreakState.status` is neither
4198
4237
  `starting-after-task` nor `on-break` -- a committed break waiting for the lead's current work to
4199
4238
  finish is not given more -- and a provider answers a `join` from a lead on such a break `failed`.
4200
4239
  The request stands for another lead, as it does when this one is already on a call.
@@ -4345,17 +4384,17 @@ about whether anybody has to act on it, and the difference is a lead's entire ac
4345
4384
 
4346
4385
  | `break` | Means |
4347
4386
  | --- | --- |
4348
- | `awaiting-decision` | Somebody has to decide. This is the lead's queue. |
4387
+ | `awaiting-approval` | Somebody has to decide. This is the lead's queue. |
4349
4388
  | `granted` | Decided yes, but Omni has not told the provider to stop yet. Work continues and nobody needs to decide. |
4350
4389
  | `starting-after-task` | Already granted; it begins when their current task ends. Nobody needs to act. |
4351
4390
 
4352
4391
  Those three are the only values that appear here. `not-requested` is absence — omit `break`
4353
4392
  instead. `on-break` is `availability: "on-break"`, and a denial transitions to `not-requested`,
4354
- so neither survives to be reported. It is otherwise the same `BreakApproval` the member's own
4393
+ so neither survives to be reported. It is otherwise the same `BreakStatus` the member's own
4355
4394
  break state uses, rather than a parallel vocabulary for the lead's view, so the two cannot drift
4356
4395
  apart.
4357
4396
 
4358
- Omni offers Approve and Deny only while a member is `awaiting-decision`, shows `granted` as agreed
4397
+ Omni offers Approve and Deny only while a member is `awaiting-approval`, shows `granted` as agreed
4359
4398
  but not started, and shows `starting-after-task` as settled.
4360
4399
 
4361
4400
  **Live status, not a record.** The provider derives it from what is true now — not stored, not
@@ -4980,8 +5019,8 @@ covers a `diagnostic`: shown where the agent works, and counted.
4980
5019
 
4981
5020
  ### `break-state`
4982
5021
 
4983
- Replaces this provider's complete `break` object. Its `approval` uses the canonical
4984
- `not-requested`, `awaiting-decision`, `granted`, `starting-after-task` and
5022
+ Replaces this provider's complete `break` object. Its `status` uses the canonical
5023
+ `not-requested`, `awaiting-approval`, `granted`, `starting-after-task` and
4985
5024
  `on-break` states defined under Breaks; the event also carries the corresponding may-ask state,
4986
5025
  reasons, retry details, and any forced break.
4987
5026
 
@@ -4990,7 +5029,7 @@ Each state is also held to the one before it. A commit's states, `starting-after
4990
5029
  break, which says so with `forced`: in effect at once, or `starting-after-task` while the member
4991
5030
  finishes the call they are on (`stream.breakState.commitBeforeGrant`); and a break never moves backwards —
4992
5031
  from `on-break` or `starting-after-task` to a grant or a request, or from `granted` to
4993
- `awaiting-decision` — a new request passes through `not-requested` (`stream.breakState.backwards`).
5032
+ `awaiting-approval` — a new request passes through `not-requested` (`stream.breakState.backwards`).
4994
5033
  `exerciseAdapter` holds the stream to that from the connect snapshot on;
4995
5034
  `assertBreakFollowsItsRequests` holds any sequence.
4996
5035
 
@@ -5012,14 +5051,14 @@ provider rechecks current permission and state atomically before acting. The pro
5012
5051
  the resulting break/task state before publication and serializes those publications. The host
5013
5052
  validates the envelope, transition and full task consistency before replacing local state. A
5014
5053
  request result does not optimistically advance break state, and a delayed command cannot apply
5015
- to a later attempt merely because its approval happens to look compatible. Keep at most one
5054
+ to a later attempt merely because its status happens to look compatible. Keep at most one
5016
5055
  unresolved operation per provider/attempt; recovery must resolve uncertainty before another
5017
5056
  operation is dispatched. The source must fence delayed operations against its own attempt state.
5018
5057
 
5019
- Normal order is `not-requested` → `awaiting-decision` → `granted` →
5058
+ Normal order is `not-requested` → `awaiting-approval` → `granted` →
5020
5059
  `starting-after-task` → `on-break` → `not-requested`. Auto-approval may go directly to
5021
5060
  `granted`; a commit with no outstanding work may go directly to `on-break`. Denial/cancel
5022
- returns a precommit request to `not-requested`; an authorized agent or lead end returns a committed
5061
+ returns a precommit request to `not-requested`; an explicit agent end returns a committed
5023
5062
  break there. Same-state restatements are allowed. An evidenced forced break is the explicit
5024
5063
  exception to requesting/granting, and must carry its forced actor/state. Neither skipped
5025
5064
  publication nor a host-local guess creates another exception. A later normal attempt starts
@@ -5029,7 +5068,7 @@ A fresh authoritative snapshot establishes a new baseline; do not run this event
5029
5068
  check across it. A snapshot may legitimately establish a later request or an already active
5030
5069
  break after reconnect. Its freshness must be established by the source/adapter's ordered
5031
5070
  snapshot/publication boundary and fencing of obsolete callbacks and reads. A delayed old
5032
- snapshot is not detectable from break approval alone. Neither envelope time nor event ID
5071
+ snapshot is not detectable from break status alone. Neither envelope time nor event ID
5033
5072
  supplies a break revision. Do not suppress all requests after an active break: a later attempt
5034
5073
  passes through `not-requested`, and then may request again. Do not replay earlier ProviderEvents
5035
5074
  after recovery. Raw backend break handlers must preserve source order too; this helper does
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.94",
3
+ "version": "0.1.95",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",