@aiaiai-pt/martha-cli 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/index.js +114 -49
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,99 @@
2
2
 
3
3
  All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to semver — `0.x` releases may include breaking changes between minor versions.
4
4
 
5
+ ## [0.57.0] — 2026-08-03
6
+
7
+ ### Fixed — `bootstrap --mode agent` produced an agent that could not do anything
8
+
9
+ Provisioned from scratch, the agent came back `✓ ready` and then, asked a
10
+ question in a channel, invented shell commands and fabricated four collections
11
+ that do not exist. Three separate holes, all of them ours:
12
+
13
+ - **No tools.** `--intents rag` granted the CLIENT functions and never the
14
+ agent's, because agent mode was composed client-side while the server's own
15
+ `agent` block — adopt-or-create with chat takeover, bind to the principal,
16
+ grant the intent's AGENT functions — was gated to human mode. That gate is
17
+ now open to integration mode (`agent_name` required: a service account is not
18
+ a person, so the name is never defaulted), and the CLI makes **one** call
19
+ instead of re-implementing it.
20
+ - **Nothing to read.** Intents grant retrieval *verbs*; the *nouns* are a
21
+ separate UMS grant. New `--collections <list>` grants the agent read on them
22
+ by name, slug or id — and when it is omitted, provisioning now says
23
+ `granted NO collections — its retrieval tools will return nothing`, because
24
+ "ready" must never mean "ready and blind".
25
+ - **A prompt the harness overrode.** buzz-acp tells agents they can run shell
26
+ commands; our default said only "don't emit `buzz messages send`". The
27
+ prompt for a newly created agent now states it has no shell and must never
28
+ write output it did not receive from a tool.
29
+
30
+ Also: the agent's system prompt applies to sessions created *after* it — an
31
+ adapter holding open sessions keeps the old one until restarted.
32
+
33
+ ### Server
34
+
35
+ `POST /principals/bootstrap` accepts `agent` + `agent_name` in integration
36
+ mode, plus `agent_system_prompt` (applied only on create — never overwrites a
37
+ persona someone has edited) and `collections`. The response's `agent` block
38
+ gains `granted_collections`. Collection grants are issued after commit as UMS
39
+ `application` grantees with `agent_<uuid>` ids — UMS has no `agent` grantee
40
+ type and 422s the bare uuid, which is invisible unless you read the skip list.
41
+
42
+ ## [0.56.0] — 2026-08-03
43
+
44
+ ### Changed — Martha agents follow the conversation by default
45
+
46
+ The harness definition now pins `BUZZ_ACP_SUBSCRIBE=all` with
47
+ `BUZZ_ACP_KINDS=9,46010,40007,7`.
48
+
49
+ Buzz's default is `subscribe=mentions`, which filters every message that does
50
+ not `#p`-tag the agent — so a **DM got no reply**, and neither did a thread
51
+ reply or a follow-up question. The previous release left the pair out of the
52
+ definition on the reasoning that overriding Buzz's default would be
53
+ surprising; the actual result was that every single agent needed both
54
+ variables typed into Edit Agent by hand. Runtime-wide behaviour is exactly
55
+ what a runtime definition is for.
56
+
57
+ The two values are pinned together because `all` without kinds is a wildcard
58
+ subscription — typing indicators and presence updates arrive as prompts. The
59
+ set is buzz-acp's own mentions-mode set (chat, approval, reminder) plus
60
+ reactions, which `acp-serve` classifies as feedback and answers with silence.
61
+
62
+ For an agent that should only speak when named, set
63
+ `BUZZ_ACP_SUBSCRIBE=mentions` on that agent — per-agent env overrides the
64
+ definition. Existing agents pick this up after
65
+ `martha channels install-harness buzz --force` and a restart.
66
+
67
+ ## [0.55.2] — 2026-08-03
68
+
69
+ ### Fixed — a baked `MARTHA_PROFILE` is a default, and we didn't say so
70
+
71
+ 0.55.0 let `install-harness --use-profile` put `MARTHA_PROFILE` in the harness
72
+ definition, and the output read as though that bound the Buzz agent to a Martha
73
+ agent. It doesn't: the definition is shared by **every** agent on the Martha
74
+ runtime, so a second Buzz agent would answer as the first one's Martha agent —
75
+ its own name and Nostr key in front of one brain, which is the opposite of what
76
+ a second agent is for.
77
+
78
+ Nothing to change in the mechanism, which already supports the right model:
79
+ per-agent env (Buzz's Stack A layer 3b) overrides definition env (layer 2b), so
80
+ `MARTHA_PROFILE=<its own profile>` in Edit Agent gives each Buzz agent its own
81
+ Martha agent — still one variable, still no secrets. `install-harness` now says
82
+ this after a successful write, `--use-profile --help` says it, and the guide has
83
+ it as a warning box.
84
+
85
+ ## [0.55.1] — 2026-08-03
86
+
87
+ ### Fixed — `definitions apply` no longer reports success on writes that don't land (#1147)
88
+
89
+ `kind: Agent` payloads are validated at load time against the server's
90
+ persisted-field set: a wrongly-nested `definition:` wrapper or any unknown
91
+ top-level field now fails with a validation error naming the field and the
92
+ expected level — instead of `updated → vN` while the server silently
93
+ discards the value. `--dry-run` surfaces the same rejection. And applying a
94
+ definition against a soft-deleted target now reactivates it (`is_active:
95
+ true`) instead of reporting `1 unchanged`, unless the local file explicitly
96
+ declares `is_active: false`.
97
+
5
98
  ## [0.55.0] — 2026-08-03
6
99
 
7
100
  ### Added — a profile can carry the agent's credential, so nothing is typed by hand
package/dist/index.js CHANGED
@@ -11149,7 +11149,7 @@ init_config();
11149
11149
  init_errors();
11150
11150
 
11151
11151
  // src/version.ts
11152
- var CLI_VERSION = "0.55.0";
11152
+ var CLI_VERSION = "0.57.0";
11153
11153
 
11154
11154
  // src/commands/sessions.ts
11155
11155
  function relativeTime(iso) {
@@ -14584,62 +14584,66 @@ var BOOTSTRAP_PATH = "/api/admin/definitions/principals/bootstrap";
14584
14584
  function parseIntents(raw) {
14585
14585
  return (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
14586
14586
  }
14587
+ var CHANNEL_AGENT_PROMPT_SUFFIX = `
14588
+
14589
+ TOOLS
14590
+ You have no shell and no terminal. Your tools are function calls, and they ` + "are the only way you can learn anything. Never write `<bash>`, shell commands, or code " + "fences pretending to run one, and never write output you did not receive from a tool. " + "If a tool returns nothing, say it returned nothing." + `
14591
+
14592
+ DELIVERY
14593
+ Reply with the message content ONLY — delivery is handled for you. Never ` + "output delivery commands (e.g. `buzz messages send`).";
14587
14594
  async function bootstrapAgentPrincipal(ctx, agent, opts = {}) {
14588
14595
  const A = `${AGENTS_PATH}/${encodeURIComponent(agent)}`;
14589
14596
  const steps = [];
14590
14597
  const warnings = [];
14591
- let def = null;
14598
+ let existing = null;
14592
14599
  try {
14593
- def = await ctx.api.get(A);
14600
+ existing = await ctx.api.get(A);
14594
14601
  } catch (e) {
14595
14602
  if (!(e instanceof MarthaAPIError && e.status === 404))
14596
14603
  throw e;
14597
14604
  }
14598
- if (!def) {
14599
- if (!opts.prompt) {
14600
- throw new CLIError(`Agent '${agent}' not found.`, 3 /* NotFound */, `Pass --prompt "<system prompt>" to create it, or create it first with: martha agents create --name ${agent} --reasoning martha --prompt "..."`);
14601
- }
14602
- const deliveryNote = `
14603
-
14604
- When a message asks you to reply in a channel, reply with the ` + "message content ONLY — delivery is handled for you. Never output " + "delivery commands (e.g. `buzz messages send`).";
14605
- def = await ctx.api.post(AGENTS_PATH, {
14606
- name: agent,
14607
- system_prompt: opts.prompt + deliveryNote,
14608
- agent_type: "cloud",
14609
- runs_as_chat: true,
14610
- description: "Agent principal for external drivers (#1117)"
14611
- });
14612
- steps.push(`created Martha-hosted agent '${agent}' (runs_as_chat)`);
14613
- } else {
14614
- if (String(def.agent_type ?? "") === "external") {
14615
- throw new CLIError(`Agent '${agent}' is self-hosted (reasoning runs in your own service). ` + `An agent principal drives a Martha-hosted agent — recreate it with --reasoning martha.`, 4 /* Validation */);
14616
- }
14617
- if (def.runs_as_chat !== true) {
14618
- await ctx.api.put(A, { runs_as_chat: true });
14619
- steps.push("enabled chat takeover (runs_as_chat)");
14620
- }
14605
+ if (existing && String(existing.agent_type ?? "") === "external") {
14606
+ throw new CLIError(`Agent '${agent}' is self-hosted (reasoning runs in your own service). ` + `An agent principal drives a Martha-hosted agent — recreate it with --reasoning martha.`, 4 /* Validation */);
14607
+ }
14608
+ if (!existing && !opts.prompt) {
14609
+ throw new CLIError(`Agent '${agent}' not found.`, 3 /* NotFound */, `Pass --prompt "<system prompt>" to create it, or create it first with: martha agents create --name ${agent} --reasoning martha --prompt "..."`);
14621
14610
  }
14622
- const agentName = String(def?.name ?? agent);
14623
14611
  const boot = await ctx.api.post(BOOTSTRAP_PATH, {
14624
14612
  mode: "integration",
14625
- label: opts.label ?? agentName,
14626
- intents: parseIntents(opts.intents)
14613
+ label: opts.label ?? agent,
14614
+ intents: parseIntents(opts.intents),
14615
+ agent: true,
14616
+ agent_name: agent,
14617
+ agent_system_prompt: opts.prompt ? opts.prompt + CHANNEL_AGENT_PROMPT_SUFFIX : undefined,
14618
+ collections: parseIntents(opts.collections)
14627
14619
  });
14628
14620
  if (!boot.service_account || boot.client_id == null) {
14629
14621
  throw new CLIError("Bootstrap did not return service-account credentials.", 1 /* Error */, "Check that your user has the admin realm role (integration mode requires it).");
14630
14622
  }
14623
+ const agentName = boot.agent?.name ?? agent;
14631
14624
  steps.push(boot.adopted ? `reusing principal '${boot.service_account.client_id}' (same secret — recovery, not rotation)` : `provisioned principal '${boot.service_account.client_id}'`);
14632
- const granted = await ctx.api.get(`/api/admin/definitions/clients/${boot.client_id}/agents`);
14633
- const names = granted.map((g) => String(g.name ?? g.agent_name ?? ""));
14634
- if (!names.includes(agentName)) {
14635
- await ctx.api.post(`/api/admin/definitions/clients/${boot.client_id}/agents`, { agent_name: agentName });
14636
- steps.push(`bound '${agentName}' to client ${boot.client_id}`);
14637
- } else {
14638
- steps.push(`'${agentName}' already bound to client ${boot.client_id}`);
14625
+ steps.push(boot.agent?.adopted ? `bound existing agent '${agentName}' to client ${boot.client_id}` : `created agent '${agentName}' (runs_as_chat) and bound it to client ${boot.client_id}`);
14626
+ const fns = boot.agent?.granted_functions ?? [];
14627
+ steps.push(fns.length ? `granted its tools: ${fns.join(", ")}` : "granted NO tools — pass --intents rag (or documents/tasks) or it can do nothing");
14628
+ const cols = boot.agent?.granted_collections ?? [];
14629
+ steps.push(cols.length ? `granted it read on: ${cols.join(", ")}` : "granted NO collections — its retrieval tools will return nothing until you pass --collections");
14630
+ if (!fns.length) {
14631
+ warnings.push("no tools: this agent will answer from the model alone.");
14632
+ }
14633
+ if (fns.length && !cols.length) {
14634
+ warnings.push(`it can call list_collections and will get an empty list — grant one: martha collections grant <name> --to agent:${boot.agent?.agent_id ?? "<id>"}`);
14639
14635
  }
14640
- const others = names.filter((n) => n && n !== agentName);
14641
- if (others.length) {
14642
- warnings.push(`client ${boot.client_id} also has agent(s) ${others.join(", ")} — chat takeover requires exactly ONE bound agent; revoke the others or use a fresh --label.`);
14636
+ for (const s of boot.skipped ?? []) {
14637
+ warnings.push(`skipped ${s.name}: ${s.reason}`);
14638
+ }
14639
+ try {
14640
+ const bound = await ctx.api.get(`/api/admin/definitions/clients/${boot.client_id}/agents`);
14641
+ const others = (Array.isArray(bound) ? bound : []).map((g) => String(g.name ?? g.agent_name ?? "")).filter((n) => n && n !== agentName);
14642
+ if (others.length) {
14643
+ warnings.push(`client ${boot.client_id} also has agent(s) ${others.join(", ")} — chat takeover requires exactly ONE bound agent; revoke the others or use a fresh --label.`);
14644
+ }
14645
+ } catch {
14646
+ warnings.push(`could not read back client ${boot.client_id}'s agents — verify with: martha agents doctor ${agentName}`);
14643
14647
  }
14644
14648
  return {
14645
14649
  agent: agentName,
@@ -14705,7 +14709,7 @@ function printAgentPrincipalReady(r, savedProfile) {
14705
14709
  }
14706
14710
  function registerPrincipalCommands(program2) {
14707
14711
  const principals = program2.command("principals").description("Provision principals — who may act, with what credential (mirrors the API's principals/bootstrap)");
14708
- principals.command("bootstrap").description("Provision a principal. --mode human: your run-as client (what `martha init` wraps). " + "--mode integration: headless service account. --mode agent: service account bound " + "to ONE Martha-hosted agent (chat takeover) so the credential acts as the agent — " + "the shape an external ACP driver (Buzz, Zed) needs.").requiredOption("--mode <mode>", "human | integration | agent").option("--agent <name>", "Agent to bind (agent mode)").option("--prompt <text>", "System prompt — creates the agent when it doesn't exist yet (agent mode)").option("--label <label>", "Label for the service-account client id (default: the agent name in agent mode)").option("--intents <list>", "Comma-separated tool intents to grant (rag, documents, workflows, tasks)").option("--save-profile <name>", "Save the minted credential as a Martha profile, so anything that runs as this agent needs only MARTHA_PROFILE=<name> — no secret to copy").action(async (opts) => {
14712
+ principals.command("bootstrap").description("Provision a principal. --mode human: your run-as client (what `martha init` wraps). " + "--mode integration: headless service account. --mode agent: service account bound " + "to ONE Martha-hosted agent (chat takeover) so the credential acts as the agent — " + "the shape an external ACP driver (Buzz, Zed) needs.").requiredOption("--mode <mode>", "human | integration | agent").option("--agent <name>", "Agent to bind (agent mode)").option("--prompt <text>", "System prompt — creates the agent when it doesn't exist yet (agent mode)").option("--label <label>", "Label for the service-account client id (default: the agent name in agent mode)").option("--intents <list>", "Comma-separated tool intents to grant (rag, documents, workflows, tasks)").option("--collections <list>", "Comma-separated collections (name, slug or id) the agent may read. Intents grant the retrieval TOOLS; without this they return nothing").option("--save-profile <name>", "Save the minted credential as a Martha profile, so anything that runs as this agent needs only MARTHA_PROFILE=<name> — no secret to copy").action(async (opts) => {
14709
14713
  const ctx = createContext({
14710
14714
  profileOverride: program2.opts().profile,
14711
14715
  apiUrlOverride: program2.opts().apiUrl,
@@ -14720,7 +14724,8 @@ function registerPrincipalCommands(program2) {
14720
14724
  const result = await bootstrapAgentPrincipal(ctx, opts.agent, {
14721
14725
  label: opts.label,
14722
14726
  prompt: opts.prompt,
14723
- intents: opts.intents
14727
+ intents: opts.intents,
14728
+ collections: opts.collections
14724
14729
  });
14725
14730
  const savedProfile = opts.saveProfile ? saveAgentProfile(ctx, opts.saveProfile, result) : null;
14726
14731
  if (isJson) {
@@ -14795,6 +14800,8 @@ function buzzHarnessDefinition(opts = {}) {
14795
14800
  args: ["acp-serve", "--post-reply"],
14796
14801
  env: {
14797
14802
  BUZZ_ACP_MULTIPLE_EVENT_HANDLING: "queue",
14803
+ BUZZ_ACP_SUBSCRIBE: "all",
14804
+ BUZZ_ACP_KINDS: DEFAULT_BUZZ_KINDS,
14798
14805
  ...opts.profile ? { MARTHA_PROFILE: opts.profile } : {}
14799
14806
  },
14800
14807
  installInstructionsUrl: "https://docs.martha.nomadriver.co/cli/buzz",
@@ -15217,7 +15224,7 @@ function assertSupportedChannel(kind) {
15217
15224
  }
15218
15225
  function registerChannelCommands(program2) {
15219
15226
  const channels = program2.command("channels").description("Put a Martha agent in an external conversation channel — from Buzz (install-harness) or from Martha (connect)");
15220
- channels.command("install-harness <type>").description("Register Martha as a runtime in Buzz Desktop (a tier-3 custom harness definition). " + "Desktop then owns the agent's keypair, attestation and lifecycle — we only declare how to start the adapter.").option("--app-data <dir>", "Buzz Desktop app-data dir (default: auto-detect)").option("--id <id>", "Harness id", BUZZ_HARNESS_ID).option("--label <label>", "Name shown in Buzz's runtime picker", "Martha").option("--command <bin>", "Executable Buzz should spawn", "martha").option("--use-profile <name>", "Bake MARTHA_PROFILE into the definition, so Buzz's Edit Agent needs no credentials at all (mint one with: martha principals bootstrap --mode agent --agent <n> --save-profile <name>). Named --use-profile because the global --profile picks which profile this COMMAND talks to.").option("--print", "Print the definition instead of writing it").option("--force", "Overwrite an existing definition").action(async (type, opts) => {
15227
+ channels.command("install-harness <type>").description("Register Martha as a runtime in Buzz Desktop (a tier-3 custom harness definition). " + "Desktop then owns the agent's keypair, attestation and lifecycle — we only declare how to start the adapter.").option("--app-data <dir>", "Buzz Desktop app-data dir (default: auto-detect)").option("--id <id>", "Harness id", BUZZ_HARNESS_ID).option("--label <label>", "Name shown in Buzz's runtime picker", "Martha").option("--command <bin>", "Executable Buzz should spawn", "martha").option("--use-profile <name>", "Bake MARTHA_PROFILE into the definition as the DEFAULT for every agent on this runtime, so Edit Agent needs nothing. A per-agent MARTHA_PROFILE in Edit Agent overrides it — that is how two Buzz agents become two different Martha agents. Named --use-profile because the global --profile picks which profile this COMMAND talks to.").option("--print", "Print the definition instead of writing it").option("--force", "Overwrite an existing definition").action(async (type, opts) => {
15221
15228
  assertSupportedChannel(type);
15222
15229
  const def = buzzHarnessDefinition({
15223
15230
  id: opts.id,
@@ -15382,19 +15389,30 @@ function installHarnessNotes(def) {
15382
15389
  ...profile ? [
15383
15390
  source_default.cyan(" 2. …that's it."),
15384
15391
  source_default.dim(` The definition names profile '${profile}'; the credential behind it
15385
- ` + ` stays in this machine's credentials file. Nothing to type in Buzz.`)
15392
+ ` + ` stays in this machine's credentials file. Nothing to type in Buzz.`),
15393
+ "",
15394
+ source_default.dim(`A second Martha agent? '${profile}' is the DEFAULT for every agent on
15395
+ ` + `this runtime — the definition is shared. Give the next one its own
15396
+ ` + `Martha agent by setting ONE env var on it in Edit Agent, which wins
15397
+ ` + `over the definition:`),
15398
+ source_default.cyan(` MARTHA_PROFILE=<its own profile>`),
15399
+ source_default.dim(` (mint that profile with: martha principals bootstrap --mode agent
15400
+ ` + " --agent <name> --save-profile <name>)")
15386
15401
  ] : [
15387
15402
  source_default.cyan(" 2. Edit Agent → env vars → MARTHA_CLIENT_ID / MARTHA_CLIENT_SECRET"),
15388
15403
  source_default.dim(" (a definition file is shared config, not a secret store)"),
15389
- source_default.dim(" Skip this step next time: mint with `martha principals bootstrap\n" + " --mode agent --agent <n> --save-profile <name>`, then re-run this\n" + " with --use-profile <name>.")
15404
+ source_default.dim(" One variable instead of two secrets: mint with `martha principals\n" + " bootstrap --mode agent --agent <n> --save-profile <name>`, then set\n" + ` MARTHA_PROFILE=<name> here — per agent, so each Buzz agent can be a
15405
+ ` + ` different Martha agent. Re-run this with --use-profile <name> to make
15406
+ ` + " one of them the default for the runtime.")
15390
15407
  ],
15391
15408
  "",
15392
15409
  source_default.dim(`Desktop owns the rest: the Nostr keypair in your keyring, the NIP-OA
15393
15410
  ` + "attestation, spawning buzz-acp, and attaching the agent to channels."),
15394
15411
  "",
15395
- source_default.dim(`If you set BUZZ_ACP_SUBSCRIBE=all on the agent, you MUST also set
15396
- ` + `BUZZ_ACP_KINDS=${DEFAULT_BUZZ_KINDS} — 'all' with no kinds is a wildcard
15397
- ` + "subscription and turns typing and presence events into prompts.")
15412
+ source_default.dim(`Every agent on this runtime follows the conversation: subscribe=all with
15413
+ ` + `kinds ${DEFAULT_BUZZ_KINDS} pinned, so DMs, thread replies and follow-ups
15414
+ ` + `reach it without being re-@mentioned. Want one that only answers when
15415
+ ` + `named? Set BUZZ_ACP_SUBSCRIBE=mentions on that agent in Edit Agent.`)
15398
15416
  ];
15399
15417
  }
15400
15418
 
@@ -15996,6 +16014,37 @@ var SERVER_GENERATED_FIELDS = new Set([
15996
16014
  "tenant_id"
15997
16015
  ]);
15998
16016
  var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
16017
+ var AGENT_WRITE_FIELDS = new Set([
16018
+ "name",
16019
+ "display_name",
16020
+ "description",
16021
+ "agent_type",
16022
+ "system_prompt",
16023
+ "llm_config",
16024
+ "loop_config",
16025
+ "runs_as_chat",
16026
+ "agent_metadata",
16027
+ "auth_method",
16028
+ "webhook_url",
16029
+ "webhook_secret",
16030
+ "webhook_timeout_s",
16031
+ "is_active",
16032
+ "self_grant_enabled",
16033
+ "self_grant_allowlist",
16034
+ "max_self_grant_scope",
16035
+ "self_grant_max_pending",
16036
+ "self_grant_collection_roots",
16037
+ ...AGENT_MANAGED_FIELDS
16038
+ ]);
16039
+ function validateAgentWriteBody(body, source) {
16040
+ if ("definition" in body) {
16041
+ throw new CLIError(`'definition:' wrapper in ${source} is not supported for kind: Agent — ` + `Agent fields live at the top level. Move the nested keys ` + `(${Object.keys(body.definition ?? {}).join(", ") || "…"}) ` + `up one level.`, 4 /* Validation */);
16042
+ }
16043
+ const unknown = Object.keys(body).filter((k) => !AGENT_WRITE_FIELDS.has(k));
16044
+ if (unknown.length > 0) {
16045
+ throw new CLIError(`Unknown Agent field${unknown.length > 1 ? "s" : ""} in ${source}: ` + `${unknown.map((k) => `'${k}'`).join(", ")} — the server would ` + `silently discard ${unknown.length > 1 ? "them" : "it"}. ` + `Supported top-level fields: ${[...AGENT_WRITE_FIELDS].sort().join(", ")}.`, 4 /* Validation */);
16046
+ }
16047
+ }
15999
16048
  function loadDefinitions(inputPath) {
16000
16049
  const resolved = path8.resolve(inputPath);
16001
16050
  if (!fs8.existsSync(resolved)) {
@@ -16084,6 +16133,9 @@ function toLocalDefinition(raw, source) {
16084
16133
  }
16085
16134
  const typedKind = kind;
16086
16135
  const normalizedBody = typedKind === "Function" || typedKind === "Workflow" ? normalizeDefinitionWriteBody(typedKind, body) : body;
16136
+ if (typedKind === "Agent") {
16137
+ validateAgentWriteBody(normalizedBody, source);
16138
+ }
16087
16139
  return { kind: typedKind, name, body: normalizedBody, source };
16088
16140
  }
16089
16141
  async function computeOperations(defs, api) {
@@ -16110,6 +16162,10 @@ async function computeOperations(defs, api) {
16110
16162
  continue;
16111
16163
  }
16112
16164
  const changedFields = diffFields(def.body, remote);
16165
+ const needsReactivation = remote.is_active === false && def.body.is_active !== false;
16166
+ if (needsReactivation && !changedFields.includes("is_active")) {
16167
+ changedFields.push("is_active");
16168
+ }
16113
16169
  if (changedFields.length === 0) {
16114
16170
  ops.push({
16115
16171
  kind: def.kind,
@@ -16123,7 +16179,8 @@ async function computeOperations(defs, api) {
16123
16179
  name: def.name,
16124
16180
  action: "update",
16125
16181
  changedFields,
16126
- source: def.source
16182
+ source: def.source,
16183
+ reactivate: needsReactivation || undefined
16127
16184
  });
16128
16185
  }
16129
16186
  }
@@ -16204,7 +16261,12 @@ function renderPlan(ops) {
16204
16261
  grouped.get(op.kind).push(op);
16205
16262
  }
16206
16263
  const nameW = Math.max(16, ...ops.map((o) => o.name.length)) + 2;
16207
- for (const kind of ["Function", "Workflow", "Agent", "Trigger"]) {
16264
+ for (const kind of [
16265
+ "Function",
16266
+ "Workflow",
16267
+ "Agent",
16268
+ "Trigger"
16269
+ ]) {
16208
16270
  const kindOps = grouped.get(kind);
16209
16271
  if (!kindOps)
16210
16272
  continue;
@@ -16285,6 +16347,9 @@ async function executeApply(api, ops, defs, warnings) {
16285
16347
  port_warnings: portWarnings.length > 0 ? portWarnings : undefined
16286
16348
  });
16287
16349
  } else {
16350
+ if (op.reactivate) {
16351
+ body.is_active = true;
16352
+ }
16288
16353
  const res = await api.put(`${apiPath}/${encodeURIComponent(op.name)}`, body);
16289
16354
  if (op.kind === "Function" || op.kind === "Workflow") {
16290
16355
  verifyDefinitionUpdateResponse(op.kind, body, res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {