@oxygen-agent/cli 1.836.4 → 1.837.5

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/README.md CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation, visit https://oxygen-agent.com/docs. For support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.836.4
37
+ Version: 1.837.5
@@ -39,6 +39,9 @@ const MUTATING_VERBS = new Set([
39
39
  "bind", "buy", "call", "cancel", "chat-action", "claim", "clear",
40
40
  "comment", "configure", "connect", "create", "decide", "delete", "delist", "disable",
41
41
  "disconnect", "dispatch", "done", "draft", "duplicate", "edit", "emit", "enable", "enroll",
42
+ // Exact leaf, not a `field` prefix: `support admin field-set` writes a Plain
43
+ // Thread field, while a future `field-get` would be a read.
44
+ "field-set",
42
45
  "file", "forward", "grant", "harvest", "history", "import", "insert", "interrupt", "invite",
43
46
  "label-add", "label-remove", "launch", "log", "login", "logout", "mark-all-read", "mark-read", "materialize", "merge",
44
47
  "migrate",
@@ -47,8 +50,12 @@ const MUTATING_VERBS = new Set([
47
50
  "register", "reject", "relink", "remove", "rename", "reorder", "reply", "request",
48
51
  "replay", "rerun", "rescan", "resend", "reset", "resolve", "restore", "resume",
49
52
  "retry", "retype", "revoke", "rotate", "run", "save", "schedule",
50
- "seed", "select", "send", "set", "setup", "share", "solve",
51
- "start", "stop", "subscribe", "sync", "synthesize", "tag", "unarchive", "unpublish", "unshare",
53
+ "seed", "select", "send", "set", "setup", "share", "snooze", "solve",
54
+ "start", "stop", "subscribe", "sync", "synthesize", "tag",
55
+ // `support admin todo` moves a Plain Thread back into the queue. The noun
56
+ // reads like a state, but the command is the transition into it.
57
+ "todo",
58
+ "unarchive", "unpublish", "unshare",
52
59
  "unbind", "unsubscribe", "update", "upload", "upsert", "use", "warm-send", "warmup",
53
60
  "withdraw", "write",
54
61
  ]);
@@ -80,6 +87,8 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
80
87
  "mailboxes delete",
81
88
  "support admin done",
82
89
  "support admin reply",
90
+ // A bare call reads the Plain workspace and prints the plan; --apply writes.
91
+ "support admin setup",
83
92
  "workflows webhooks rotate",
84
93
  ]);
85
94
  export function buildCommandManifest(program, binaryName) {
package/dist/index.js CHANGED
@@ -2966,6 +2966,50 @@ The final readback must show ticket.status triaging, plain_status TODO, the In p
2966
2966
  await handleSupportAdminUpdateRequest("priority", ticketId, options, {
2967
2967
  priority: readOption(options.priority),
2968
2968
  });
2969
+ }))
2970
+ .addCommand(new Command("snooze")
2971
+ .description("Park a live Thread until a named time, so work blocked on a release, a provider, or a scheduled retry stops reading as unanswered (staff only).")
2972
+ .argument("<ticketId>", "Plain Thread ID (th_...).")
2973
+ .option("--days <n>", "Snooze for this many days.")
2974
+ .option("--hours <n>", "Snooze for this many hours. Combined with --days when both are given.")
2975
+ .requiredOption("--confirm-message <messageId>", "Confirm the exact latest customer-visible message ID. A Thread whose customer just wrote must be answered, not snoozed past.")
2976
+ .option("--json", "Print a JSON envelope.")
2977
+ .action(async (ticketId, options) => {
2978
+ await handleSupportAdminUpdateRequest("snooze", ticketId, options, {
2979
+ duration_seconds: readSupportSnoozeSeconds(options),
2980
+ confirm_message_id: readOption(options.confirmMessage),
2981
+ });
2982
+ }))
2983
+ .addCommand(new Command("todo")
2984
+ .description("Return a snoozed Thread to the queue when its blocker clears (staff only). Never reopens a Done Thread: Plain does that on real customer activity.")
2985
+ .argument("<ticketId>", "Plain Thread ID (th_...).")
2986
+ .option("--json", "Print a JSON envelope.")
2987
+ .action(async (ticketId, options) => {
2988
+ await handleSupportAdminUpdateRequest("todo", ticketId, options, {});
2989
+ }))
2990
+ .addCommand(new Command("assign")
2991
+ .description("Hand a live Thread to a named human, for escalation past an agent's authority or judgment boundary (staff only).")
2992
+ .argument("<ticketId>", "Plain Thread ID (th_...).")
2993
+ .requiredOption("--user <email>", "The staff email to assign the Thread to.")
2994
+ .option("--allow-takeover", "Required to move a Thread that already has an owner. Without it, an owned Thread is refused rather than silently reassigned.")
2995
+ .option("--json", "Print a JSON envelope.")
2996
+ .action(async (ticketId, options) => {
2997
+ await handleSupportAdminUpdateRequest("assign", ticketId, options, {
2998
+ assignee_email: readOption(options.user),
2999
+ allow_takeover: options.allowTakeover === true,
3000
+ });
3001
+ }))
3002
+ .addCommand(new Command("field-set")
3003
+ .description("Record OXYGEN workflow state on a live Thread as structured, filterable Plain data — which version and commit carry a fix, and the PR that shipped it (staff only).")
3004
+ .argument("<ticketId>", "Plain Thread ID (th_...).")
3005
+ .requiredOption("--field <key>", "oxygen_fix_version | oxygen_fix_sha | oxygen_fix_pr. Customer-intake fields are read-only evidence and cannot be written here.")
3006
+ .requiredOption("--value <value>", "The value to record.")
3007
+ .option("--json", "Print a JSON envelope.")
3008
+ .action(async (ticketId, options) => {
3009
+ await handleSupportAdminUpdateRequest("field_set", ticketId, options, {
3010
+ field_key: readOption(options.field),
3011
+ field_value: readOption(options.value),
3012
+ });
2969
3013
  }))
2970
3014
  .addCommand(new Command("label-add")
2971
3015
  .description("Add an active Plain label by external ID (staff only).")
@@ -3085,6 +3129,21 @@ The final readback must show ticket.status triaging, plain_status TODO, the In p
3085
3129
  },
3086
3130
  }));
3087
3131
  }), { hidden: true })
3132
+ .addCommand(new Command("setup")
3133
+ .description("Reconcile the shared Plain workspace against the taxonomy OXYGEN declares in code: request-type labels and intake fields, plus the workflow-state labels and release fields the support loop writes. Previews by default and never sends anything to a customer (staff only).")
3134
+ .option("--apply", "Apply the previewed plan. Requires --confirm-plan with the exact plan_hash you just read.")
3135
+ .option("--confirm-plan <hash>", "The plan_hash from the immediately preceding preview. A workspace that drifted in between fails closed.")
3136
+ .option("--json", "Print a JSON envelope.")
3137
+ .action(async (options) => {
3138
+ if (options.apply !== true) {
3139
+ await handleAsyncAction("support admin setup", options, () => requestOxygen("/api/cli/admin/support/setup"));
3140
+ return;
3141
+ }
3142
+ await handleAsyncAction("support admin setup", options, () => requestOxygen("/api/cli/admin/support/setup", {
3143
+ method: "POST",
3144
+ body: { confirm_plan_hash: readOption(options.confirmPlan) },
3145
+ }));
3146
+ }))
3088
3147
  .addCommand(new Command("events")
3089
3148
  .description("Read-only poll of legacy OXYGEN archive events for cutover compatibility; never use it as the live queue (staff only).")
3090
3149
  .option("--after <cursor>", "If has_more=true, pass next_cursor to continue that scan. If false, persist the non-null watermark_cursor for later polls. A watermark poll intentionally replays the settled 24h window, not only unseen events; dedupe every page by (ticket_ref, version).")
@@ -21898,6 +21957,28 @@ async function handleSupportAdminUpdateRequest(action, ticketId, options, fields
21898
21957
  body: { action, ...fields },
21899
21958
  }));
21900
21959
  }
21960
+ /**
21961
+ * Plain snoozes by duration, but humans and agents think in days and hours.
21962
+ * Requiring at least one keeps `snooze` from silently meaning "five minutes".
21963
+ */
21964
+ function readSupportSnoozeSeconds(options) {
21965
+ const days = readSupportSnoozeUnit(options.days, "--days");
21966
+ const hours = readSupportSnoozeUnit(options.hours, "--hours");
21967
+ const seconds = days * 24 * 60 * 60 + hours * 60 * 60;
21968
+ if (seconds <= 0) {
21969
+ throw new OxygenError("invalid_request", "Pass --days and/or --hours to say how long the Thread should stay parked.", { exitCode: 1 });
21970
+ }
21971
+ return seconds;
21972
+ }
21973
+ function readSupportSnoozeUnit(raw, flag) {
21974
+ if (raw === undefined)
21975
+ return 0;
21976
+ const value = Number(raw.trim());
21977
+ if (!Number.isInteger(value) || value < 0) {
21978
+ throw new OxygenError("invalid_request", `${flag} must be a whole, non-negative number.`, { exitCode: 1 });
21979
+ }
21980
+ return value;
21981
+ }
21901
21982
  async function handleSupportAdminWorkflowAction(ticketId, options) {
21902
21983
  try {
21903
21984
  const body = buildSupportAdminWorkflowBody(options);
@@ -1,4 +1,4 @@
1
- export declare const OXYGEN_VERSION = "1.836.4";
1
+ export declare const OXYGEN_VERSION = "1.837.5";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
3
3
  export declare const MANAGED_INBOX_MINIMUM_CLI_VERSION = "1.326.2";
4
4
  export declare const SUPPORT_AGENT_REPLY_MINIMUM_CLI_VERSION = "1.747.0";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.836.4";
1
+ export const OXYGEN_VERSION = "1.837.5";
2
2
  // The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
3
3
  // operational route. Raising it hard-rejects every older CLI from the entire
4
4
  // product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.836.4",
3
+ "version": "1.837.5",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",