drafted 1.25.2 → 1.25.4

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/cli/drafted.mjs CHANGED
@@ -2244,6 +2244,79 @@ async function minionSetEnabled(id, enabled, format) {
2244
2244
  emitMinionResult(format, { status: 'ok', id: c.id, slug: c.slug, name: c.name });
2245
2245
  }
2246
2246
 
2247
+ // ── Actions: propose an outward effect and read its decision ─────────────
2248
+ // The seam a Causeway automation's approval@1 step uses (Approvals inbox, A5/C4):
2249
+ // propose, then poll status until a person decides in the org's Approvals inbox.
2250
+ // Approving stays a browser-only act in Drafted; nothing here can approve.
2251
+ const actionCmd = program.command('action').description('Propose an outward effect for a person to approve, and read its decision');
2252
+
2253
+ function actionState(actions) {
2254
+ const st = (actions || []).map((a) => a.status);
2255
+ if (!st.length) return 'unknown';
2256
+ if (st.every((s) => s === 'proposed')) return 'waiting';
2257
+ if (st.includes('rejected')) return 'rejected';
2258
+ if (st.includes('failed')) return 'failed';
2259
+ if (st.every((s) => s === 'executed')) return 'executed';
2260
+ return 'approved';
2261
+ }
2262
+
2263
+ async function actionApi(method, apiPath, org, body) {
2264
+ requireLogin();
2265
+ const server = getServerUrl().replace(/\/$/, '');
2266
+ const headers = { ...(org ? { 'X-Drafted-Org': org } : {}), ...(body ? { 'Content-Type': 'application/json' } : {}) };
2267
+ const res = await authFetch(`${server}${apiPath}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
2268
+ const data = await res.json().catch(() => ({}));
2269
+ return { ok: res.ok, status: res.status, data };
2270
+ }
2271
+
2272
+ actionCmd
2273
+ .command('propose')
2274
+ .description('Propose from JSON {type,payload} or {actions:[{type,payload}...]}, plus optional reason and assignee (an org member email or user id; default you). Reads --file or stdin.')
2275
+ .option('--file <path>', 'JSON file (default: stdin)')
2276
+ .option('--org <org>', 'org (id or slug) to propose in')
2277
+ .option('--format <fmt>', 'output format: json or text', 'text')
2278
+ .action(async (opts) => {
2279
+ let req;
2280
+ try { req = opts.file ? JSON.parse(readFileSync(opts.file, 'utf8')) : readStdinJSON(); }
2281
+ catch { console.error('invalid JSON request'); process.exit(1); }
2282
+ const body = Array.isArray(req.actions) ? { actions: req.actions } : { actions: [{ type: req.type, payload: req.payload || {} }] };
2283
+ if (req.reason) body.reason = String(req.reason);
2284
+ if (req.assignee) body.assignee = String(req.assignee);
2285
+ const r = await actionApi('POST', '/api/actions', opts.org, body);
2286
+ const out = r.ok
2287
+ ? { status: 'ok', groupId: r.data.groupId, url: r.data.receipt && r.data.receipt.url, assignee: r.data.assignee || null }
2288
+ : { status: 'error', error: r.data.detail || r.data.error || `HTTP ${r.status}`, code: r.data.error || null };
2289
+ if (opts.format === 'json') console.log(JSON.stringify(out));
2290
+ else console.log(r.ok ? `${out.groupId}\t${out.url || ''}` : `error\t${out.error}`);
2291
+ if (!r.ok) process.exit(1);
2292
+ });
2293
+
2294
+ actionCmd
2295
+ .command('status <groupId>')
2296
+ .description('A proposal\'s decision: waiting, approved, executed, failed or rejected, with who decided and why')
2297
+ .option('--org <org>', 'org (id or slug) it was proposed in')
2298
+ .option('--format <fmt>', 'output format: json or text', 'text')
2299
+ .action(async (groupId, opts) => {
2300
+ const r = await actionApi('GET', `/api/actions/groups/${encodeURIComponent(groupId)}`, opts.org);
2301
+ if (!r.ok) {
2302
+ const out = { status: 'error', error: r.data.detail || r.data.error || `HTTP ${r.status}`, code: r.data.error || null };
2303
+ if (opts.format === 'json') console.log(JSON.stringify(out)); else console.log(`error\t${out.error}`);
2304
+ process.exit(1);
2305
+ }
2306
+ const acts = r.data.actions || [];
2307
+ const out = {
2308
+ status: 'ok',
2309
+ groupId,
2310
+ state: actionState(acts),
2311
+ decidedBy: r.data.decidedBy ? r.data.decidedBy.name : null,
2312
+ note: r.data.decisionNote || null,
2313
+ assignee: r.data.assignee ? r.data.assignee.name : null,
2314
+ steps: acts.map((a) => ({ seq: a.seq, type: a.type, status: a.status, error: (a.result && a.result.error) || null })),
2315
+ };
2316
+ if (opts.format === 'json') console.log(JSON.stringify(out));
2317
+ else console.log(`${out.state}\t${out.decidedBy || ''}\t${out.note || ''}`);
2318
+ });
2319
+
2247
2320
  const minionCmd = program.command('minion').description('Minion management (checklist-driven intake surfaces). Requires the agent allowlist.');
2248
2321
 
2249
2322
  minionCmd
package/mcp/server.mjs CHANGED
@@ -2829,7 +2829,10 @@ tool('trigger', {
2829
2829
  dailyLimit: z.number().optional().describe('[create|update] max deliveries per UTC day (default 50) — the cost cap.'),
2830
2830
  enabled: z.boolean().optional().describe('[update] enable/disable the trigger (the kill switch). Re-enabling resets the failure counter.'),
2831
2831
  payload: z.object({}).passthrough().optional().describe('[test] synthetic payload for the test delivery (default { test: true }).'),
2832
- executor: z.enum(['minion', 'queue']).optional().describe('[create|update] who runs deliveries: "minion" (default) runs the server-side agent immediately; "queue" parks deliveries for a LOCAL agent to poll via pending/claim/complete — use this to relay webhooks to yourself or a scheduled Causeway session.'),
2832
+ executor: z.enum(['minion', 'queue', 'automation']).optional().describe('[create|update] who runs deliveries: "minion" (default) runs the server-side agent immediately; "queue" parks deliveries for a LOCAL agent to poll via pending/claim/complete — use this to relay webhooks to yourself or a scheduled Causeway session; "automation" runs a skill\'s chain (automationSkill) at a pinned version in Drafted for each delivery, payload as the untrusted input trigger.json — only an org OWNER can create one or move its pin, and the chain must use built-ins Drafted runs (template@1, approval@1, when:).'),
2833
+ automationSkill: z.string().optional().describe('[create|update, executor=automation] the skill whose chain runs: its slug or id, in the project\'s org.'),
2834
+ automationChain: z.string().optional().describe('[create|update, executor=automation] which named chain (default "default").'),
2835
+ automationSkillVersion: z.number().optional().describe('[create|update, executor=automation] the pinned skill version to run (default: the skill\'s current version). Moving the pin is publishing: owner only.'),
2833
2836
  deliveryId: z.string().optional().describe('[complete] delivery ID returned by claim'),
2834
2837
  ok: z.boolean().optional().describe('[complete] whether the claimed work succeeded (default true)'),
2835
2838
  error: z.string().optional().describe('[complete] error note when ok=false'),
@@ -2846,9 +2849,10 @@ tool('trigger', {
2846
2849
  const scoped = (fn) => project ? withProjectOverride(project, fn) : fn();
2847
2850
  switch (action) {
2848
2851
  case 'create': {
2849
- if (!args.name || !args.promptTemplate) throw new Error('name and promptTemplate required for action=create');
2850
- const body = { name: args.name, promptTemplate: args.promptTemplate, projectId: project.id };
2852
+ if (!args.name || (!args.promptTemplate && args.executor !== 'automation')) throw new Error('name and promptTemplate required for action=create (an automation needs automationSkill instead of a prompt)');
2853
+ const body = { name: args.name, promptTemplate: args.promptTemplate || '', projectId: project.id };
2851
2854
  if (args.executor) body.executor = args.executor;
2855
+ for (const k of ['automationSkill', 'automationChain', 'automationSkillVersion']) if (args[k] !== undefined) body[k] = args[k];
2852
2856
  if (args.signingSecret) body.signingSecret = args.signingSecret;
2853
2857
  if (args.dailyLimit !== undefined) body.dailyLimit = args.dailyLimit;
2854
2858
  const created = await scoped(() => api('POST', '/api/triggers', body));
@@ -2874,6 +2878,7 @@ tool('trigger', {
2874
2878
  if (args.dailyLimit !== undefined) body.dailyLimit = args.dailyLimit;
2875
2879
  if (args.signingSecret !== undefined) body.signingSecret = args.signingSecret || null;
2876
2880
  if (args.executor) body.executor = args.executor;
2881
+ for (const k of ['automationSkill', 'automationChain', 'automationSkillVersion']) if (args[k] !== undefined) body[k] = args[k];
2877
2882
  if (Object.keys(body).length === 0) throw new Error('At least one field is required for action=update');
2878
2883
  return ok(await api('PATCH', `/api/triggers/${args.triggerId}`, body));
2879
2884
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.25.2",
3
+ "version": "1.25.4",
4
4
  "description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
5
5
  "type": "module",
6
6
  "bin": {