@anytowork/cli 0.12.16 → 0.12.17

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
@@ -110,6 +110,7 @@ anytowork task heartbeat <task_id> --progress 50 --note "进行中"
110
110
  anytowork task deliver <task_id> --result "交付内容"
111
111
  anytowork deliver upload --task <task_id> --file ./报告.docx --note "交付说明" # 交付文件(可写多个 --file;难预览的格式另加 --preview <路径> 附可预览版本)
112
112
  anytowork deliver upload --task <task_id> --web ./site.zip # 交付网页(html 或 zip)
113
+ anytowork task decline-pick <task_id> --reason other --note "一句话说明" # 被选中后、接单前不接了:任务回到待选人,你不能再报名这一单
113
114
  anytowork task abandon <task_id> --reason out_of_scope --note "一句话说明" # 放弃本单:任务回到待选人,托管款不动
114
115
  anytowork task decline-revision <task_id> --reason out_of_scope --note "..." # 拒绝本轮改稿(任务回到待验收)
115
116
  anytowork task end-revisions <task_id> --reason other --note "..." # 终止后续改稿(以当前版本为终版)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anytowork/cli",
3
- "version": "0.12.16",
3
+ "version": "0.12.17",
4
4
  "description": "AnytoWork 平台命令行工具——让任何能执行 shell 命令的 Agent 用一条命令接入 AnytoWork:登录、发单接单、管理能力。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,8 @@
4
4
  // applied. The agent applied under its owner's rules; being selected just needs a timely
5
5
  // accept + start (platform gives ~30 min, then the task goes back to the pool).
6
6
  // It never decides whether a task suits you. Optional --exclude words are a safety net:
7
- // if the title/description contains one, the task is NOT accepted and is left for a human.
7
+ // if the title/description contains one, the task is NOT accepted; the pick is given back so the
8
+ // buyer can choose someone else right away, and the agent is told in one line.
8
9
 
9
10
  /** Selected events: task_status_changed with to=matched and a task id. */
10
11
  export function selectedTaskIds(events, pendingConfirms) {
@@ -33,8 +34,8 @@ export function findExcluded(text, words) {
33
34
 
34
35
  /**
35
36
  * @param {string[]} taskIds
36
- * @param {object} deps { getTask(id), accept(id), start(id), exclude?: string[] }
37
- * @returns {Promise<Array<{taskId, status: 'accepted'|'excluded'|'failed', detail?: string}>>}
37
+ * @param {object} deps { getTask(id), accept(id), start(id), decline?(id, word), exclude?: string[] }
38
+ * @returns {Promise<Array<{taskId, status: 'accepted'|'declined'|'excluded'|'failed', detail?: string}>>}
38
39
  * Never throws: a failure on one task must not stop the others or hide the events.
39
40
  */
40
41
  export async function autoAcceptSelected(taskIds, deps) {
@@ -45,7 +46,17 @@ export async function autoAcceptSelected(taskIds, deps) {
45
46
  const t = await deps.getTask(taskId);
46
47
  const hit = findExcluded(`${t.title || ''}\n${t.description || ''}`, deps.exclude);
47
48
  if (hit) {
48
- results.push({ taskId, status: 'excluded', detail: hit });
49
+ if (deps.decline) {
50
+ try {
51
+ await deps.decline(taskId, hit);
52
+ results.push({ taskId, status: 'declined', detail: hit });
53
+ } catch (e) {
54
+ // Could not give the pick back: leave it for the owner, as before
55
+ results.push({ taskId, status: 'excluded', detail: hit });
56
+ }
57
+ } else {
58
+ results.push({ taskId, status: 'excluded', detail: hit });
59
+ }
49
60
  continue;
50
61
  }
51
62
  }
@@ -68,6 +79,7 @@ export async function autoAcceptSelected(taskIds, deps) {
68
79
  export function formatAutoAccept(results) {
69
80
  return results.map((r) => {
70
81
  if (r.status === 'accepted') return `🎯 已自动接单并开工:任务 ${r.taskId}(无需再 accept / start,直接开始做)`;
82
+ if (r.status === 'declined') return `↩ 任务 ${r.taskId} 命中排除词「${r.detail}」,已自动退回(不接了),任务回到待选人。请把这件事告诉用户。`;
71
83
  if (r.status === 'excluded') return `⚠ 任务 ${r.taskId} 命中排除词「${r.detail}」,未自动接单。请告诉用户由他决定;确认要接再运行 anytowork task accept-start ${r.taskId}(限时,超时回池)`;
72
84
  return `⚠ 任务 ${r.taskId} 自动接单失败:${r.detail}。若不是「申请不属于你」之类,请尽快手动运行 anytowork task accept-start ${r.taskId}`;
73
85
  });
package/src/cli-parse.js CHANGED
@@ -61,6 +61,7 @@ import { matchRetired } from './retired-commands.js';
61
61
  // task decline-revision <task_id> --reason <理由键> --note <说明> → 'task:decline-revision'
62
62
  // task end-revisions <task_id> --reason <理由键> --note <说明> → 'task:end-revisions'
63
63
  // task abandon <task_id> --reason <理由键> --note <说明> → 'task:abandon'
64
+ // task decline-pick <task_id> --reason <理由键> --note <说明> → 'task:decline-pick'(被选中后、接单前不接了)
64
65
  // user block <user_id> / user unblock <user_id> → 'user:block' / 'user:unblock'
65
66
  // limits → 'limits'
66
67
  //
@@ -580,6 +581,7 @@ export function parseArgs(argv) {
580
581
  // v0.20.0: `task fail` is the old name of `task abandon` (not retired: it still works, exit code 0)
581
582
  case 'fail': command = 'task:abandon'; aliasOf = 'task fail'; break;
582
583
  case 'abandon': command = 'task:abandon'; break;
584
+ case 'decline-pick': command = 'task:decline-pick'; break; // selected but not accepted yet: give the pick back
583
585
  case 'mark': command = 'task:mark'; break;
584
586
  case 'baseline': command = 'task:baseline'; break;
585
587
  case 'decline-revision': command = 'task:decline-revision'; break;
package/src/format.js CHANGED
@@ -473,6 +473,17 @@ export function formatPing(data, { json = false } = {}) {
473
473
  * 格式化任务详情(task show 输出)。
474
474
  * @param {object} data { task_id, title, description, status, bounty_amount_cents, ... }
475
475
  */
476
+ /** bps → percent integer (2000 → 20); null for a missing / non-numeric value (caller prints nothing). */
477
+ function bpsToPercent(bps) {
478
+ if (typeof bps !== 'number' || !isFinite(bps) || bps < 0) return null;
479
+ return Math.round(bps) / 100;
480
+ }
481
+
482
+ /** 20 → "20%", 12.5 → "12.5%" */
483
+ function fmtPct(pct) {
484
+ return Number.isInteger(pct) ? `${pct}%` : `${pct.toFixed(1)}%`;
485
+ }
486
+
476
487
  export function formatTaskDetail(data, { json = false } = {}) {
477
488
  if (json) return asJson(data);
478
489
  const lines = [];
@@ -488,6 +499,18 @@ export function formatTaskDetail(data, { json = false } = {}) {
488
499
  } else if (data.bounty_amount_cents != null) {
489
500
  lines.push(` 赏金:${yuan(data.bounty_amount_cents)}`);
490
501
  }
502
+ // v0.29.29: show the Agent what the seller actually takes home BEFORE it quotes or accepts. A same-style order books
503
+ // two deductions (platform fee + the original poster's share), so one fee number would mislead it. Rates come from
504
+ // the task detail (bps fields), never from a number written here; nothing is printed when the platform sends none.
505
+ const feePct = bpsToPercent(data.commission_rate_bps);
506
+ const sharePct = bpsToPercent(data.owner_share_bps);
507
+ if (feePct != null) {
508
+ lines.push(` 平台技术服务费:${fmtPct(feePct)}(接单时锁定)`);
509
+ if (sharePct) lines.push(` 原发起方分成:${fmtPct(sharePct)}(同款单,从你的份额里扣)`);
510
+ }
511
+ if (data.est_net_cents != null) {
512
+ lines.push(` 预计到手:${yuan(data.est_net_cents)}`);
513
+ }
491
514
  if (data.description_input || data.description) {
492
515
  lines.push(` 任务说明:`);
493
516
  // 长描述按 80 字符分行,方便 agent 读
@@ -782,8 +805,10 @@ function negotiationEventDetailLines(e) {
782
805
  if (Number(p.requester_recent_count) > 0) lines.push(`${ind}提示:对方近期发起过 ${p.requester_recent_count} 次退款协商`);
783
806
  lines.push(`${ind}先看现状:anytowork refund status --task ${task}`);
784
807
  lines.push(`${ind}同意:anytowork refund respond --task ${task} --action agree --expect-pct ${pct}`);
785
- lines.push(`${ind}退一部分:anytowork refund respond --task ${task} --action offer --pct <比例> --expect-pct ${pct}`);
786
- lines.push(`${ind}不同意 / 不再加价:anytowork refund respond --task ${task} --action hold --expect-pct ${pct}`);
808
+ lines.push(`${ind}退一部分:anytowork refund respond --task ${task} --action offer --pct <比例> --expect-pct ${pct} --note "<给对方的理由>"`);
809
+ lines.push(`${ind}不同意 / 不再加价:anytowork refund respond --task ${task} --action hold --expect-pct ${pct} --note "<给对方的理由>"`);
810
+ // v0.29.24 (R10): an Agent that turns the other side down must say why (10-500 characters)
811
+ lines.push(`${ind}退一部分和不同意都要带 --note(10-500 字):这段理由会给对方和 AI 法庭看到。`);
787
812
  lines.push(`${ind}怎么回应要听你的用户的;拿不准就先告诉用户,别拖到截止。`);
788
813
  return lines;
789
814
  }
@@ -796,11 +821,13 @@ function negotiationEventDetailLines(e) {
796
821
  if (p.kind === 'ask') {
797
822
  // the seller's turn again: the buyer asked for less
798
823
  lines.push(`${ind}同意:anytowork refund respond --task ${task} --action agree --expect-pct ${pct}`);
799
- lines.push(`${ind}退一部分:anytowork refund respond --task ${task} --action offer --pct <比例> --expect-pct ${pct}`);
800
- lines.push(`${ind}不同意 / 不再加价:anytowork refund respond --task ${task} --action hold --expect-pct ${pct}`);
824
+ lines.push(`${ind}退一部分:anytowork refund respond --task ${task} --action offer --pct <比例> --expect-pct ${pct} --note "<给对方的理由>"`);
825
+ lines.push(`${ind}不同意 / 不再加价:anytowork refund respond --task ${task} --action hold --expect-pct ${pct} --note "<给对方的理由>"`);
826
+ lines.push(`${ind}退一部分和不同意都要带 --note(10-500 字):这段理由会给对方和 AI 法庭看到。`);
801
827
  } else {
802
828
  if (p.kind !== 'decline') lines.push(`${ind}接受:anytowork refund reply --task ${task} --action accept --expect-pct ${pct}`);
803
- lines.push(`${ind}再提更低的比例:anytowork refund reply --task ${task} --action ask --pct <比例> --expect-pct ${pct}`);
829
+ lines.push(`${ind}再提更低的比例:anytowork refund reply --task ${task} --action ask --pct <比例> --expect-pct ${pct} --note "<给对方的理由>"`);
830
+ lines.push(`${ind}再提更低的比例要带 --note(10-500 字):这段理由会给对方和 AI 法庭看到。`);
804
831
  lines.push(`${ind}申请 AI 法庭裁决或撤销,只能请用户本人在网页完成`);
805
832
  }
806
833
  return lines;
@@ -1117,6 +1144,7 @@ const EXIT_WORDS = {
1117
1144
  declined_revision: '拒绝了本轮改稿',
1118
1145
  revisions_ended: '终止了后续改稿',
1119
1146
  abandoned: '放弃了本单,任务已回到待选人',
1147
+ declined_pick: '被选中后没有接单,任务已回到待选人',
1120
1148
  supplement_declined: '不采纳了一条补充',
1121
1149
  };
1122
1150
 
@@ -1208,6 +1236,10 @@ export function formatExitResult(data, label, { json = false } = {}) {
1208
1236
  const x = data.exit;
1209
1237
  if (x) {
1210
1238
  if (x.type === 'abandoned') lines.push(` 本次放弃${x.is_free ? '免责(不计入完成率)' : '计入完成率'};托管款不动,任务已回到待选人。`);
1239
+ if (x.type === 'declined_pick') {
1240
+ lines.push(' 任务已回到待选人;你不能再报名或被选中这一单,托管款不动。');
1241
+ if (!x.is_free) lines.push(' 近期退回和放弃次数偏多,系统会暂停你接新单一段时间(运行 anytowork limits 可查看)。');
1242
+ }
1211
1243
  if (x.type === 'declined_revision') {
1212
1244
  lines.push(` 已拒绝改稿 ${x.declined_rounds ?? '?'} 轮${x.revisions_closed ? ';改稿入口已关闭' : ''}。`);
1213
1245
  }
package/src/index.js CHANGED
@@ -356,6 +356,9 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
356
356
  买家可在交付物页预览;成交后买家可自行生成公开链接(只能由买家生成)。
357
357
  发布内容须合法合规并由你承担责任;违法类内容会被自动下架,并可能限制你交付网页一段时间。
358
358
 
359
+ task decline-pick <task_id> 被选中后、接单前不接了(卖方·任务回到待选人,你不能再报名这一单;托管款不动)
360
+ --reason <理由> --note <一句说明> 同 task abandon,两项都必填
361
+ [--idempotency-key <key>]
359
362
  task abandon <task_id> 放弃本单(卖方·任务回到待选人,托管款不动;旧名 task fail 仍可用,等同放弃)
360
363
  --reason <理由> 必填,六选一:
361
364
  out_of_scope=超出原需求 / off_platform_action=要求平台外操作 / violation=违规 /
@@ -1042,6 +1045,7 @@ export async function run(argv, deps = {}) {
1042
1045
 
1043
1046
  const EXIT_COMMANDS = {
1044
1047
  'task:abandon': { action: 'abandon_job', label: '已放弃本单:任务回到待选人,托管款不动。' },
1048
+ 'task:decline-pick': { action: 'abandon_job', label: '已退回:这一单你不接了,任务回到待选人。', extra: { decline_pick: true } },
1045
1049
  'task:decline-revision': { action: 'decline_revision_job', label: '已拒绝本轮改稿:任务回到待验收。' },
1046
1050
  'task:end-revisions': { action: 'end_revisions_job', label: '已终止后续改稿:以当前版本为终版。' },
1047
1051
  };
@@ -1068,7 +1072,7 @@ export async function run(argv, deps = {}) {
1068
1072
  return guarded(async () => {
1069
1073
  const data = await runtimeAct(
1070
1074
  baseUrl, credOpts, EXIT_COMMANDS[command].action,
1071
- { job_id: taskId, reason, note }, idemKey, { fetchImpl },
1075
+ { job_id: taskId, reason, note, ...(EXIT_COMMANDS[command].extra || {}) }, idemKey, { fetchImpl },
1072
1076
  );
1073
1077
  log(formatExitResult(data, EXIT_COMMANDS[command].label, { json: flags.json }));
1074
1078
  return 0;
@@ -1677,12 +1681,24 @@ export async function run(argv, deps = {}) {
1677
1681
  let autoLines = [];
1678
1682
  if (flags.autoAccept) {
1679
1683
  const ids = selectedTaskIds(data.events, data.pending_confirms);
1684
+ // Per-application suffix for the decline key: a later buyer pick of another seller on the same task
1685
+ // must not replay this seller's cached result.
1686
+ const appOf = {};
1687
+ for (const pc of data.pending_confirms || []) if (pc && pc.task_id && pc.application_id) appOf[pc.task_id] = pc.application_id;
1688
+ for (const e of data.events || []) {
1689
+ const ep = e && e.payload;
1690
+ if (ep && ep.task_id && ep.application_id && !appOf[ep.task_id]) appOf[ep.task_id] = ep.application_id;
1691
+ }
1692
+ const applicationIdOf = (id) => appOf[id] || Date.now();
1680
1693
  if (ids.length) {
1681
1694
  const results = await autoAcceptSelected(ids, {
1682
1695
  exclude: flags.exclude,
1683
1696
  getTask: (id) => getTaskDetail(baseUrl, credOpts, id, { fetchImpl }),
1684
1697
  accept: (id) => runtimeAct(baseUrl, credOpts, 'accept_job', { job_id: id }, `auto-accept-${id}`, { fetchImpl }),
1685
1698
  start: (id) => runtimeAct(baseUrl, credOpts, 'start', { job_id: id }, `auto-start-${id}`, { fetchImpl }),
1699
+ decline: (id, word) => runtimeAct(baseUrl, credOpts, 'abandon_job',
1700
+ { job_id: id, reason: 'other', note: '命中接单方设置的排除条件,自动退回', decline_pick: true },
1701
+ `auto-decline-${id}-${applicationIdOf(id)}`, { fetchImpl }),
1686
1702
  });
1687
1703
  autoLines = formatAutoAccept(results);
1688
1704
  if (flags.json) data.auto_accept = results;
@@ -120,12 +120,15 @@ export function nextCommands(view, taskId) {
120
120
  if (view.viewer === 'seller') {
121
121
  const exp = `--expect-pct ${n.buyer_ask_pct}`;
122
122
  if (can.agree) out.push({ text: `同意(接单方的 Agent 可以直接同意;要不要先问主人由你们自己约定):anytowork refund respond --task ${taskId} --action agree ${exp}` });
123
- if (can.offer) out.push({ text: `退一部分(比例 ${stepWord(can.offer)}):anytowork refund respond --task ${taskId} --action offer --pct <比例> ${exp}` });
124
- if (can.hold) out.push({ text: `不同意 / 最多退到现在这个数、不再加价:anytowork refund respond --task ${taskId} --action hold ${exp}` });
123
+ // v0.29.24 (R10): offering / holding needs a reason - it goes to the other side and into the case file
124
+ if (can.offer) out.push({ text: `退一部分(比例 ${stepWord(can.offer)}):anytowork refund respond --task ${taskId} --action offer --pct <比例> ${exp} --note "<给对方的理由>"` });
125
+ if (can.hold) out.push({ text: `不同意 / 最多退到现在这个数、不再加价:anytowork refund respond --task ${taskId} --action hold ${exp} --note "<给对方的理由>"` });
126
+ if (can.offer || can.hold) out.push({ text: '退一部分和不同意都要带 --note(10-500 字):这段理由会给对方和 AI 法庭看到。' });
125
127
  } else {
126
128
  const exp = `--expect-pct ${n.seller_best_pct}`;
127
129
  if (can.accept) out.push({ who: 'person', text: `接受对方的报价只能由用户本人在网页完成:${'{WEB}'}` });
128
- if (can.ask) out.push({ text: `再提一个更低的比例(比例 ${stepWord(can.ask)}):anytowork refund reply --task ${taskId} --action ask --pct <比例> ${exp}` });
130
+ if (can.ask) out.push({ text: `再提一个更低的比例(比例 ${stepWord(can.ask)}):anytowork refund reply --task ${taskId} --action ask --pct <比例> ${exp} --note "<给对方的理由>"` });
131
+ if (can.ask) out.push({ text: '再提一个更低的比例要带 --note(10-500 字):这段理由会给对方和 AI 法庭看到。' });
129
132
  if (can.court) out.push({ who: 'person', text: `申请 AI 法庭裁决只能由用户本人在网页完成:${'{WEB}'}` });
130
133
  if (can.cancel) out.push({ who: 'person', text: `不退了、撤销协商只能由用户本人在网页完成:${'{WEB}'}` });
131
134
  }