@oxygen-agent/cli 1.836.2 → 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.2
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
@@ -2931,11 +2931,22 @@ export function createProgram() {
2931
2931
  }));
2932
2932
  }))
2933
2933
  .addCommand(new Command("start")
2934
- .description("Return a live Plain Thread to Todo / In progress when OXYGEN work remains after a reply, preserving its assignee (staff only).")
2934
+ .description("Set a live Plain Thread to Plain TODO with the In progress detail when OXYGEN work remains after a reply, preserving its assignee (staff only).")
2935
2935
  .argument("<ticketId>", "Plain Thread ID (th_...).")
2936
2936
  .requiredOption("--agent", "Required safety declaration: reconcile this Thread's status as the Oxygen Support machine user.")
2937
- .requiredOption("--confirm-message <messageId>", "Apply only while this is still the exact latest customer-visible conversation-head message ID.")
2937
+ .requiredOption("--confirm-message <messageId>", "Apply only while this is still the exact latest customer-visible conversation-head message ID. From support admin get --json, use the id of the data.messages[] entry with the greatest created_at.")
2938
2938
  .option("--json", "Print a JSON envelope.")
2939
+ .addHelpText("after", `
2940
+ Safety: 0 credits, but this is a real Plain queue write. It preserves the current assignee and changes only the lifecycle to plain_status TODO with plain_status_detail ThreadStatusDetailInProgress (projected as ticket.status triaging).
2941
+
2942
+ Safe sequence:
2943
+ oxygen support admin get <thread-id> --json
2944
+ # Select the data.messages[] entry with the greatest created_at and copy its id.
2945
+ oxygen support admin start <thread-id> --agent --confirm-message <latest-message-id> --json
2946
+ oxygen support admin get <thread-id> --json
2947
+
2948
+ The final readback must show ticket.status triaging, plain_status TODO, the In progress detail, the same conversation head, and an unchanged assignee.
2949
+ `)
2939
2950
  .action(async (ticketId, options) => {
2940
2951
  await handleAsyncAction("support admin start", options, () => requestOxygen(`/api/cli/admin/support/tickets/${encodeURIComponent(ticketId)}/update`, {
2941
2952
  method: "POST",
@@ -2955,6 +2966,50 @@ export function createProgram() {
2955
2966
  await handleSupportAdminUpdateRequest("priority", ticketId, options, {
2956
2967
  priority: readOption(options.priority),
2957
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
+ });
2958
3013
  }))
2959
3014
  .addCommand(new Command("label-add")
2960
3015
  .description("Add an active Plain label by external ID (staff only).")
@@ -3074,6 +3129,21 @@ export function createProgram() {
3074
3129
  },
3075
3130
  }));
3076
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
+ }))
3077
3147
  .addCommand(new Command("events")
3078
3148
  .description("Read-only poll of legacy OXYGEN archive events for cutover compatibility; never use it as the live queue (staff only).")
3079
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).")
@@ -21887,6 +21957,28 @@ async function handleSupportAdminUpdateRequest(action, ticketId, options, fields
21887
21957
  body: { action, ...fields },
21888
21958
  }));
21889
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
+ }
21890
21982
  async function handleSupportAdminWorkflowAction(ticketId, options) {
21891
21983
  try {
21892
21984
  const body = buildSupportAdminWorkflowBody(options);
@@ -1,4 +1,4 @@
1
- export declare const OXYGEN_VERSION = "1.836.2";
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.2";
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.2",
3
+ "version": "1.837.5",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",