@oxygen-agent/cli 1.310.2 → 1.334.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +26 -10
  3. package/dist/help.js +7 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +2077 -134
  6. package/dist/runtime.js +13 -3
  7. package/dist/skills.js +192 -32
  8. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
  9. package/node_modules/@oxygen/shared/dist/billing.d.ts +52 -29
  10. package/node_modules/@oxygen/shared/dist/billing.js +77 -55
  11. package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
  12. package/node_modules/@oxygen/shared/dist/index.js +4 -0
  13. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +84 -0
  14. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +82 -0
  15. package/node_modules/@oxygen/shared/dist/recipes.d.ts +19 -0
  16. package/node_modules/@oxygen/shared/dist/recipes.js +90 -0
  17. package/node_modules/@oxygen/shared/dist/sequences.d.ts +61 -15
  18. package/node_modules/@oxygen/shared/dist/sequences.js +134 -23
  19. package/node_modules/@oxygen/shared/dist/sql-error.d.ts +25 -0
  20. package/node_modules/@oxygen/shared/dist/sql-error.js +46 -0
  21. package/node_modules/@oxygen/shared/dist/version.d.ts +2 -2
  22. package/node_modules/@oxygen/shared/dist/version.js +13 -11
  23. package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.d.ts +4 -0
  24. package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.js +18 -0
  25. package/node_modules/@oxygen/shared/dist/workflow-status-change.d.ts +61 -0
  26. package/node_modules/@oxygen/shared/dist/workflow-status-change.js +124 -0
  27. package/node_modules/@oxygen/shared/dist/workspace-agents.d.ts +65 -0
  28. package/node_modules/@oxygen/shared/dist/workspace-agents.js +67 -0
  29. package/node_modules/@oxygen/workflows/dist/index.js +86 -2
  30. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The audit record behind every workflow enable/disable (OXY-4124).
3
+ *
4
+ * Disabling a workflow is the highest-consequence write in the Action layer — it
5
+ * takes a customer's revenue automation to zero — and it used to record nothing
6
+ * but a bumped `updated_at`: no actor, no source surface, no reason. Answering
7
+ * "who turned this off?" meant a forensic pass across the control DB, the tenant
8
+ * shard, and the request log; the customer saw nothing at all (a design partner's
9
+ * only automation was silently off for 64h).
10
+ *
11
+ * The record lives in `workflow_definitions.metadata.last_status_change` — the
12
+ * column is untyped JSONB, so this needs no migration. It is the *definition*-level
13
+ * peer of the trigger-level auto-pause metadata (`auto_paused_at` /
14
+ * `auto_pause_reason`, written by `workflow-trigger-health.ts`), which already got
15
+ * this right; this module closes the asymmetry for the human disable path.
16
+ *
17
+ * Every surface (web, CLI, MCP) renders the same sentence from
18
+ * `describeWorkflowStatusChange` so "Disabled by X on Y" reads identically
19
+ * wherever a customer meets it.
20
+ */
21
+ export declare const WORKFLOW_STATUS_CHANGE_METADATA_KEY = "last_status_change";
22
+ /** The surface the transition came through. `auto_pause` is the worker health sweep. */
23
+ export type WorkflowStatusChangeSource = "web" | "cli" | "mcp" | "api" | "auto_pause" | "system";
24
+ export type WorkflowStatusChangeActor = {
25
+ type: "user" | "api_key" | "system";
26
+ /** Control-DB user id. Absent for system/auto-pause actors. */
27
+ user_id?: string;
28
+ /** Set when the call authenticated with an org API key rather than a user session. */
29
+ api_key_id?: string;
30
+ /** What the product shows the customer, e.g. "dana@acme.com" or "Oxygen (auto-pause)". */
31
+ label: string;
32
+ };
33
+ export type WorkflowStatusChange = {
34
+ status: "active" | "disabled";
35
+ /** ISO-8601 UTC. */
36
+ at: string;
37
+ source: WorkflowStatusChangeSource;
38
+ actor: WorkflowStatusChangeActor;
39
+ /** Customer-visible. Auto-pause supplies its own; humans may pass one. */
40
+ reason?: string;
41
+ };
42
+ /**
43
+ * Defensive read: the column is untyped JSONB and every row written before this
44
+ * shipped has no record at all, so a missing/garbage value must read as "unknown",
45
+ * never throw. An unparseable record is indistinguishable from no record.
46
+ */
47
+ export declare function readWorkflowStatusChange(metadata: Record<string, unknown> | null | undefined): WorkflowStatusChange | null;
48
+ /**
49
+ * Same defensive parse, one level in: for callers holding the already-projected
50
+ * `statusChange` object rather than the raw metadata bag (the CLI reads it off
51
+ * the API's JSON envelope, where it arrives as `unknown`).
52
+ */
53
+ export declare function parseWorkflowStatusChange(raw: unknown): WorkflowStatusChange | null;
54
+ /** Deterministic UTC stamp ("10 Jul 2026 15:40 UTC") — no locale dependence. */
55
+ export declare function formatWorkflowStatusChangeTimestamp(at: string): string;
56
+ /**
57
+ * The single sentence every surface shows. Web banner, `oxygen workflows
58
+ * list/get`, and the MCP payload all render this string, so the answer to
59
+ * "who turned this off?" is identical wherever the customer looks.
60
+ */
61
+ export declare function describeWorkflowStatusChange(change: WorkflowStatusChange): string;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The audit record behind every workflow enable/disable (OXY-4124).
3
+ *
4
+ * Disabling a workflow is the highest-consequence write in the Action layer — it
5
+ * takes a customer's revenue automation to zero — and it used to record nothing
6
+ * but a bumped `updated_at`: no actor, no source surface, no reason. Answering
7
+ * "who turned this off?" meant a forensic pass across the control DB, the tenant
8
+ * shard, and the request log; the customer saw nothing at all (a design partner's
9
+ * only automation was silently off for 64h).
10
+ *
11
+ * The record lives in `workflow_definitions.metadata.last_status_change` — the
12
+ * column is untyped JSONB, so this needs no migration. It is the *definition*-level
13
+ * peer of the trigger-level auto-pause metadata (`auto_paused_at` /
14
+ * `auto_pause_reason`, written by `workflow-trigger-health.ts`), which already got
15
+ * this right; this module closes the asymmetry for the human disable path.
16
+ *
17
+ * Every surface (web, CLI, MCP) renders the same sentence from
18
+ * `describeWorkflowStatusChange` so "Disabled by X on Y" reads identically
19
+ * wherever a customer meets it.
20
+ */
21
+ export const WORKFLOW_STATUS_CHANGE_METADATA_KEY = "last_status_change";
22
+ const SOURCES = new Set([
23
+ "web",
24
+ "cli",
25
+ "mcp",
26
+ "api",
27
+ "auto_pause",
28
+ "system",
29
+ ]);
30
+ const ACTOR_TYPES = new Set([
31
+ "user",
32
+ "api_key",
33
+ "system",
34
+ ]);
35
+ const SOURCE_LABELS = {
36
+ web: "the web app",
37
+ cli: "the CLI",
38
+ mcp: "MCP",
39
+ api: "the API",
40
+ auto_pause: "auto-pause",
41
+ system: "Oxygen",
42
+ };
43
+ const MONTHS = [
44
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
45
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
46
+ ];
47
+ /**
48
+ * Defensive read: the column is untyped JSONB and every row written before this
49
+ * shipped has no record at all, so a missing/garbage value must read as "unknown",
50
+ * never throw. An unparseable record is indistinguishable from no record.
51
+ */
52
+ export function readWorkflowStatusChange(metadata) {
53
+ return parseWorkflowStatusChange(metadata?.[WORKFLOW_STATUS_CHANGE_METADATA_KEY]);
54
+ }
55
+ /**
56
+ * Same defensive parse, one level in: for callers holding the already-projected
57
+ * `statusChange` object rather than the raw metadata bag (the CLI reads it off
58
+ * the API's JSON envelope, where it arrives as `unknown`).
59
+ */
60
+ export function parseWorkflowStatusChange(raw) {
61
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
62
+ return null;
63
+ const record = raw;
64
+ const status = record.status;
65
+ if (status !== "active" && status !== "disabled")
66
+ return null;
67
+ const at = record.at;
68
+ if (typeof at !== "string" || Number.isNaN(Date.parse(at)))
69
+ return null;
70
+ const source = typeof record.source === "string" && SOURCES.has(record.source)
71
+ ? record.source
72
+ : "system";
73
+ const actorRaw = record.actor;
74
+ const actorRecord = actorRaw && typeof actorRaw === "object" && !Array.isArray(actorRaw)
75
+ ? actorRaw
76
+ : {};
77
+ const actorType = typeof actorRecord.type === "string" && ACTOR_TYPES.has(actorRecord.type)
78
+ ? actorRecord.type
79
+ : "system";
80
+ const label = typeof actorRecord.label === "string" && actorRecord.label.trim() !== ""
81
+ ? actorRecord.label.trim()
82
+ : "an unknown actor";
83
+ const actor = { type: actorType, label };
84
+ if (typeof actorRecord.user_id === "string" && actorRecord.user_id !== "") {
85
+ actor.user_id = actorRecord.user_id;
86
+ }
87
+ if (typeof actorRecord.api_key_id === "string" && actorRecord.api_key_id !== "") {
88
+ actor.api_key_id = actorRecord.api_key_id;
89
+ }
90
+ const change = { status, at, source, actor };
91
+ if (typeof record.reason === "string" && record.reason.trim() !== "") {
92
+ change.reason = record.reason.trim();
93
+ }
94
+ return change;
95
+ }
96
+ /** Deterministic UTC stamp ("10 Jul 2026 15:40 UTC") — no locale dependence. */
97
+ export function formatWorkflowStatusChangeTimestamp(at) {
98
+ const date = new Date(at);
99
+ if (Number.isNaN(date.getTime()))
100
+ return at;
101
+ const day = String(date.getUTCDate()).padStart(2, "0");
102
+ const month = MONTHS[date.getUTCMonth()];
103
+ const hours = String(date.getUTCHours()).padStart(2, "0");
104
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
105
+ return `${day} ${month} ${date.getUTCFullYear()} ${hours}:${minutes} UTC`;
106
+ }
107
+ /**
108
+ * The single sentence every surface shows. Web banner, `oxygen workflows
109
+ * list/get`, and the MCP payload all render this string, so the answer to
110
+ * "who turned this off?" is identical wherever the customer looks.
111
+ */
112
+ export function describeWorkflowStatusChange(change) {
113
+ const verb = change.status === "disabled" ? "Disabled" : "Enabled";
114
+ const when = formatWorkflowStatusChangeTimestamp(change.at);
115
+ // Auto-pause is not a "who" — Oxygen paused it, and the reason carries the why.
116
+ const by = change.source === "auto_pause"
117
+ ? "Auto-paused by Oxygen"
118
+ : `${verb} by ${change.actor.label}`;
119
+ const via = change.source === "auto_pause" || change.source === "system"
120
+ ? ""
121
+ : ` via ${SOURCE_LABELS[change.source]}`;
122
+ const base = `${by}${via} on ${when}`;
123
+ return change.reason ? `${base} — ${change.reason}` : base;
124
+ }
@@ -0,0 +1,65 @@
1
+ /** The three implemented Workspace Agent specialists, by stable slug. */
2
+ export type WorkspaceAgentSlug = "inbox-reply-drafts" | "meeting-notetaker" | "knowledge-synthesis";
3
+ /**
4
+ * How the web layer builds a specialist's deep-link. A pure hint on the
5
+ * descriptor (shared can't import the web URL helpers) that agent-registry
6
+ * switches on to produce the actual `https://oxygen-agent.com/...` URL.
7
+ */
8
+ export type WorkspaceAgentDeepLinkKind = "inbox_reply_agent" | "notetaker" | "knowledge_agent";
9
+ /** A pure, serializable descriptor for one Workspace Agent specialist. */
10
+ export type WorkspaceAgentDescriptor = {
11
+ /** Stable identifier used across CLI/MCP/API. */
12
+ slug: WorkspaceAgentSlug;
13
+ /** Human title for roster rows and widgets. */
14
+ title: string;
15
+ /** One outcome-framed sentence: what the specialist produces for the user. */
16
+ description: string;
17
+ /** The OXYGEN OS primitive this specialist writes into. */
18
+ owningPrimitive: string;
19
+ /** The CLI command that configures this specialist. */
20
+ configCommand: string;
21
+ /** The `/api/cli/*` route that configures this specialist. */
22
+ configApiPath: string;
23
+ /** The MCP tools that read/write this specialist's config. */
24
+ mcpConfigTools: readonly string[];
25
+ /** One plain sentence stating the specialist's write/approval boundary. */
26
+ approvalPosture: string;
27
+ /** How the web layer resolves this specialist's deep-link. */
28
+ deepLinkKind: WorkspaceAgentDeepLinkKind;
29
+ };
30
+ /**
31
+ * A single run this specialist produced. Field names are aligned with the
32
+ * observability run vocabulary so the cross-primitive runs console can consume
33
+ * these without a parallel shape. Nulls are explicit (never omitted) so every
34
+ * consumer reads the same keys regardless of specialist.
35
+ */
36
+ export type WorkspaceAgentRunItem = {
37
+ /** The specialist that produced this run. */
38
+ slug: WorkspaceAgentSlug;
39
+ /** The run's stable id (draft id / session id / knowledge-log id). */
40
+ id: string;
41
+ /** The kind of artifact this run produced. */
42
+ kind: string;
43
+ /** The run's current status, in the producing surface's own vocabulary. */
44
+ status: string;
45
+ /** A short human summary of the run, or null when none is available. */
46
+ summary: string | null;
47
+ /** When the run was created (ISO 8601), or null when unknown. */
48
+ createdAt: string | null;
49
+ /** Managed credits this run consumed, or null when it spent none / is unknown. */
50
+ creditsUsed: number | null;
51
+ /** Deep-link to inspect this run, or null when the specialist has no per-run page. */
52
+ webUrl: string | null;
53
+ };
54
+ /**
55
+ * The roster. Exactly three entries — one per implemented specialist. Order is
56
+ * stable (it drives the default CLI/widget ordering). Adding a fourth entry is a
57
+ * deliberate product decision that requires the specialist to actually ship first.
58
+ */
59
+ export declare const WORKSPACE_AGENT_ROSTER: readonly WorkspaceAgentDescriptor[];
60
+ /** True when `value` is one of the three implemented specialist slugs. */
61
+ export declare function isWorkspaceAgentSlug(value: unknown): value is WorkspaceAgentSlug;
62
+ /** The descriptor for `slug`, or `undefined` when it is not a known specialist. */
63
+ export declare function getWorkspaceAgentDescriptor(slug: string): WorkspaceAgentDescriptor | undefined;
64
+ /** Every specialist slug, in roster order. */
65
+ export declare function workspaceAgentSlugs(): WorkspaceAgentSlug[];
@@ -0,0 +1,67 @@
1
+ // The Workspace Agent roster: the single governed lens over OXYGEN's built-in
2
+ // specialist agents. This is deliberately a *computed* registry, not a table and
3
+ // not a runtime — the "Workspace Agent" is ONE Control-layer surface, and each
4
+ // entry here is a product-defined specialist that already ships as an
5
+ // approval-gated, workflow-owned behavior elsewhere in the product. It is NOT a
6
+ // generic build-your-own-agent runtime (ADR 0006), so there is no create/define
7
+ // verb and there are no placeholder rows: the roster lists only specialists that
8
+ // are actually implemented today. CRM-hygiene, deliverability, and any other
9
+ // roadmap specialist stay OUT until they ship — the no-placeholder-surface rule.
10
+ //
11
+ // This module is a dependency-free leaf (pure descriptors + types) so it can live
12
+ // in @oxygen/shared without importing tenant-db: the actual per-specialist wiring
13
+ // (config read/write, run history, deep-links) lives in the web layer's
14
+ // agent-registry, which composes these descriptors with the existing helpers.
15
+ /**
16
+ * The roster. Exactly three entries — one per implemented specialist. Order is
17
+ * stable (it drives the default CLI/widget ordering). Adding a fourth entry is a
18
+ * deliberate product decision that requires the specialist to actually ship first.
19
+ */
20
+ export const WORKSPACE_AGENT_ROSTER = [
21
+ {
22
+ slug: "inbox-reply-drafts",
23
+ title: "Inbox reply drafts",
24
+ description: "Drafts replies to inbound prospect and customer messages so you can review, edit, and approve them before anything sends.",
25
+ owningPrimitive: "Messages",
26
+ configCommand: "oxygen inbox reply-agent",
27
+ configApiPath: "/api/cli/inbox/reply-agent",
28
+ mcpConfigTools: ["oxygen_inbox_reply_agent_get", "oxygen_inbox_reply_agent_set"],
29
+ approvalPosture: "Drafts replies only — nothing sends without your approval in the reply queue.",
30
+ deepLinkKind: "inbox_reply_agent",
31
+ },
32
+ {
33
+ slug: "meeting-notetaker",
34
+ title: "Meeting notetaker",
35
+ description: "Auto-joins meetings in its configured scope to record, transcribe, and file meeting notes onto the matching CRM records.",
36
+ owningPrimitive: "Records",
37
+ configCommand: "oxygen notetaker setup",
38
+ configApiPath: "/api/cli/notetaker/setup",
39
+ mcpConfigTools: ["oxygen_notetaker_setup", "oxygen_notetaker_status"],
40
+ approvalPosture: "Auto-records only meetings within its configured scope; each session is credit-capped and carries your consent disclosure.",
41
+ deepLinkKind: "notetaker",
42
+ },
43
+ {
44
+ slug: "knowledge-synthesis",
45
+ title: "Knowledge synthesis",
46
+ description: "Distills captured outreach and meeting learnings into workspace knowledge pages on the schedule and daily credit cap you set.",
47
+ owningPrimitive: "Knowledge Graph",
48
+ configCommand: "oxygen knowledge agent",
49
+ configApiPath: "/api/cli/knowledge/agent",
50
+ mcpConfigTools: ["oxygen_knowledge_agent_get", "oxygen_knowledge_agent_set"],
51
+ approvalPosture: "Writes working knowledge pages as logged revisions; canonical and pinned pages still require proposal approval.",
52
+ deepLinkKind: "knowledge_agent",
53
+ },
54
+ ];
55
+ /** True when `value` is one of the three implemented specialist slugs. */
56
+ export function isWorkspaceAgentSlug(value) {
57
+ return (typeof value === "string" &&
58
+ WORKSPACE_AGENT_ROSTER.some((descriptor) => descriptor.slug === value));
59
+ }
60
+ /** The descriptor for `slug`, or `undefined` when it is not a known specialist. */
61
+ export function getWorkspaceAgentDescriptor(slug) {
62
+ return WORKSPACE_AGENT_ROSTER.find((descriptor) => descriptor.slug === slug);
63
+ }
64
+ /** Every specialist slug, in roster order. */
65
+ export function workspaceAgentSlugs() {
66
+ return WORKSPACE_AGENT_ROSTER.map((descriptor) => descriptor.slug);
67
+ }
@@ -1293,13 +1293,94 @@ function validatePureFunctionSource(source, path, add) {
1293
1293
  }
1294
1294
  }
1295
1295
  }
1296
+ // Blank out comment bodies and string/template-literal *text* so the unsafe-token
1297
+ // scan matches only real code identifiers — not a bundle comment ("closed-loop
1298
+ // eval") or a string ("retrieval"), which the raw \b-boundary scan flagged as
1299
+ // false positives. A real eval(...) or new Function(...) — including inside a
1300
+ // `${ ... }` template interpolation, which stays code — is still caught.
1301
+ // @oxygen/workflows ships no AST parser, so this is a small hand-rolled lexer;
1302
+ // the vm sandbox + runtime global guards remain the actual security boundary.
1303
+ function stripCommentsAndStrings(source) {
1304
+ const out = [];
1305
+ const stack = [{ mode: "code", braces: 0 }];
1306
+ let i = 0;
1307
+ const n = source.length;
1308
+ while (i < n) {
1309
+ const top = stack[stack.length - 1];
1310
+ const ch = source[i];
1311
+ const next = i + 1 < n ? source[i + 1] : "";
1312
+ if (top.mode === "template") {
1313
+ if (ch === "\\") {
1314
+ i += 2;
1315
+ continue;
1316
+ }
1317
+ if (ch === "`") {
1318
+ stack.pop();
1319
+ i += 1;
1320
+ continue;
1321
+ }
1322
+ if (ch === "$" && next === "{") {
1323
+ stack.push({ mode: "code", braces: 0 });
1324
+ out.push(" ");
1325
+ i += 2;
1326
+ continue;
1327
+ }
1328
+ i += 1; // drop template text
1329
+ continue;
1330
+ }
1331
+ if (ch === "/" && next === "/") {
1332
+ i += 2;
1333
+ while (i < n && source[i] !== "\n")
1334
+ i += 1;
1335
+ continue;
1336
+ }
1337
+ if (ch === "/" && next === "*") {
1338
+ i += 2;
1339
+ while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
1340
+ i += 1;
1341
+ i = Math.min(i + 2, n);
1342
+ continue;
1343
+ }
1344
+ if (ch === '"' || ch === "'") {
1345
+ const quote = ch;
1346
+ i += 1;
1347
+ while (i < n && source[i] !== quote)
1348
+ i += source[i] === "\\" ? 2 : 1;
1349
+ i += 1;
1350
+ out.push('""');
1351
+ continue;
1352
+ }
1353
+ if (ch === "`") {
1354
+ stack.push({ mode: "template" });
1355
+ i += 1;
1356
+ continue;
1357
+ }
1358
+ if (ch === "}" && top.braces === 0 && stack.length > 1) {
1359
+ stack.pop();
1360
+ out.push(" ");
1361
+ i += 1;
1362
+ continue;
1363
+ }
1364
+ if (ch === "{")
1365
+ top.braces += 1;
1366
+ else if (ch === "}")
1367
+ top.braces = Math.max(0, top.braces - 1);
1368
+ out.push(ch);
1369
+ i += 1;
1370
+ }
1371
+ return out.join("");
1372
+ }
1296
1373
  function validateRecipeBundleSafety(source, path, add) {
1297
1374
  if (!source.trim()) {
1298
1375
  add(path, "empty_recipe_bundle", "Recipe bundle cannot be empty.");
1299
1376
  return;
1300
1377
  }
1378
+ // Scan only real code: comments and string literals are blanked first so a
1379
+ // token that merely appears in prose or data (e.g. "eval" in a comment) does
1380
+ // not trip the lint, while genuine calls survive.
1381
+ const scannable = stripCommentsAndStrings(source);
1301
1382
  for (const entry of UNSAFE_RECIPE_BUNDLE_PATTERNS) {
1302
- if (entry.pattern.test(source)) {
1383
+ if (entry.pattern.test(scannable)) {
1303
1384
  add(path, "unsafe_recipe_bundle", `Durable recipe bundle cannot reference '${entry.token}'. Use ctx.tools.run(), ctx.step(), ctx.now(), or ctx.uuid() instead.`);
1304
1385
  return;
1305
1386
  }
@@ -1547,7 +1628,10 @@ function isRecord(value) {
1547
1628
  function isNonEmptyString(value) {
1548
1629
  return typeof value === "string" && value.trim().length > 0;
1549
1630
  }
1550
- // --- Workflow run spend cap (max_credits) -------------------------------
1631
+ // The canonical list of internal workflow tool ids lives in @oxygen/tools
1632
+ // (OXYGEN_INTERNAL_WORKFLOW_TOOL_IDS). Both the worker's runtime registry and the
1633
+ // web's lint allowlist derive from it, so re-declaring it here would recreate
1634
+ // exactly the drift that broke crm-sync.
1551
1635
  // Live `workflows call` requests carry an approved max_credits spend cap in
1552
1636
  // the run's metadata (R-E.22(c)). These pure helpers are the single source of
1553
1637
  // truth for reading that cap and deciding whether the next paid tool step may
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.310.2",
3
+ "version": "1.334.3",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",