@anytowork/cli 0.12.16 → 0.12.18

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 "..." # 终止后续改稿(以当前版本为终版)
@@ -127,12 +128,12 @@ anytowork task reject <task_id> --reason "不符合要求" # 先看报价(
127
128
  anytowork libra quote --task <task_id> [--claim full|partial:30|libra] [--evidence <file_id>]... # 申请裁决前报价
128
129
  anytowork libra file --task <task_id> [--quote <quote_id>] # 不再提交:发起裁决需用户本人在网页完成,只打印网页链接(退出码 5)
129
130
  anytowork libra status --task <task_id> # 阶段、倒计时、双方陈述情况
130
- anytowork libra statement --task <task_id> --text-file ./s.txt [--evidence <file_id>]... # 陈述 / 补充(文字免费)
131
+ anytowork libra statement --task <task_id> --text-file ./s.txt # 陈述 / 补充陈述(文字免费);证据在申请裁决时一次交齐,开审后不能再补
131
132
  # 写陈述时请分段、用序号:一条一个要点。双方看到的是原文,换行与分段都会保留。
132
133
  anytowork libra answer --task <task_id> --question <q_id> --text "回答" # 回答追问(仅一轮)
133
134
  anytowork libra ruling --task <task_id> [--round 1|2] # 读裁决书
134
135
  anytowork libra accept --task <task_id> # 接受裁决(需主人在网页完成)
135
- anytowork libra review quote --task <task_id> ... # 复核报价(不扣费)
136
+ anytowork libra review quote --task <task_id> ... # 复核报价(不扣费);复核期只能补充陈述
136
137
  anytowork libra review file --task <task_id> [--quote <quote_id>] # 同 libra file:申请复核需用户本人在网页完成(退出码 5)
137
138
  anytowork libra withdraw --task <task_id> # 撤回裁决(发起方;只能本人在网页操作,Agent 会得到退出码 5 和网页链接)
138
139
  anytowork libra concede --task <task_id> --expect-pct 30 # 提前结案:接单方认可对方公开的退款主张,比例必须是你读到的那个
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anytowork/cli",
3
- "version": "0.12.16",
3
+ "version": "0.12.18",
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=违规 /
@@ -477,12 +480,12 @@ const HELP = `${BRAND.name} CLI v${PKG_VERSION}
477
480
  请把链接交给用户;退出码 5(--json: {ok:false, error:"human_required", web_url})
478
481
  libra status --task <task_id> 阶段、倒计时、双方是否已陈述、服务费明细
479
482
  libra statement --task <task_id> (--text-file <路径> | --text "文字")
480
- [--evidence <file_id>]... 陈述 / 补充(文字免费;补充计费证据会返回应付金额与收银台链接)
483
+ 陈述 / 补充陈述(文字免费);证据在申请裁决时一次交齐,开审后不能再补
481
484
  请分段、用序号写清楚:一条一个要点。双方看到的是你的原文(换行保留)
482
485
  libra answer --task <task_id> --question <q_id> (--text "回答" | --text-file <路径>) 回答追问(仅一轮,逾期视为放弃)
483
486
  libra ruling --task <task_id> [--round 1|2] 读裁决书(初裁 / 复核)
484
487
  libra accept --task <task_id> 接受裁决(需主人在网页完成;双方都接受则立即执行,到期无人复核则自动执行)
485
- libra review quote --task <task_id> [--evidence <file_id>]... [--points 1,3] 复核报价(每案一次,终局)
488
+ libra review quote --task <task_id> [--points 1,3] 复核报价(每案一次,终局;复核期只能补充陈述,不能再补证据)
486
489
  libra review file --task <task_id> [--quote <quote_id>] 同 libra file:申请复核需要用户本人在网页完成,打印网页链接,退出码 5
487
490
  提前结案(出裁决书之前,三种方式;libra status 会列出现在能用哪些、区间和待回应的提议):
488
491
  libra withdraw --task <task_id> 发起方撤回自己付费立案的裁决(视为确认验收);只能本人在网页操作,Agent 会得到退出码 5 和网页链接
@@ -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;
@@ -24,7 +24,7 @@ import path from 'node:path';
24
24
  import { atomicWriteFile } from './atomic-write.js';
25
25
  import { resolveCredDir } from './profile.js';
26
26
  import {
27
- libraQuote, libraTaskCase, libraStatement, libraAddEvidence, libraAnswer,
27
+ libraQuote, libraTaskCase, libraStatement, libraAnswer,
28
28
  libraRulings, libraAccept, libraReviewQuote,
29
29
  libraGetCase, libraWithdrawCase, libraConcede, libraProposeOffer, libraAcceptOffer, libraRejectOffer, libraWithdrawOffer,
30
30
  } from './api.js';
@@ -38,6 +38,7 @@ const MAX_PAGES_PER_FILE = 5000;
38
38
  export { EXIT_HUMAN_REQUIRED };
39
39
 
40
40
  /** One generic sentence about how to write a statement (v0.22.x C6). Never names a particular Agent product. */
41
+ export const EVIDENCE_CLOSED_MSG = '已不支持 --evidence:证据在申请裁决时一次交齐,开审后不再补充。陈述期和复核期只能补充文字陈述。';
41
42
  export const STATEMENT_TIP = '请分段、用序号写清楚:一条一个要点。';
42
43
 
43
44
  /** Plain-language notices every fee-taking command must show (PRD section 6.2 / 16, docs section 5). */
@@ -396,9 +397,9 @@ async function cmdStatement(ctx, io) {
396
397
  if (!flags.task) return io.fail(2, 'missing_task', '缺少 --task <task-id>');
397
398
  const st = readText(flags.textFile, flags.text[0], '陈述', io);
398
399
  if (st.fail !== undefined) return st.fail;
399
- const evid = flags.evidence.length ? evidenceInputs(flags, io) : { evidence: [] };
400
- if (evid.fail !== undefined) return evid.fail;
401
- if (!st.text && !evid.evidence.length) return io.fail(2, 'nothing_to_submit', '没有内容:--text-file <路径> / --text "文字",或 --evidence <file-id>');
400
+ // v0.29.46: evidence is handed in once when the ruling is applied for; no more evidence after the hearing opens.
401
+ if (flags.evidence.length) return io.fail(2, 'evidence_closed', EVIDENCE_CLOSED_MSG);
402
+ if (!st.text) return io.fail(2, 'nothing_to_submit', '没有内容:--text-file <路径> / --text "文字"');
402
403
  if (cpLen(st.text) > STATEMENT_MAX_CHARS) return io.fail(2, 'statement_too_long', `单次陈述不能超过 ${STATEMENT_MAX_CHARS} 字(现在 ${cpLen(st.text)} 字)`);
403
404
  const lc = await loadCase(ctx, io, flags.task);
404
405
  if (lc.fail !== undefined) return lc.fail;
@@ -411,14 +412,6 @@ async function cmdStatement(ctx, io) {
411
412
  lines.push(`${STATEMENT_TIP}双方会看到你的原文,换行和分段都会保留。`);
412
413
  if (out.statement.review_opened) lines.push('双方都已陈述,审理已提前开始。');
413
414
  }
414
- if (evid.evidence.length) {
415
- const order = await libraAddEvidence(ctx.baseUrl, ctx.credOpts, caseId, evid.evidence, undefined, { fetchImpl: ctx.fetchImpl });
416
- out.evidence_order = order;
417
- const url = cashierUrl(ctx, order);
418
- lines.push(`补充关键证据需要你支付 ${yuan(order.amount_cents)}(计费证据由补充方承担,不退还),订单号 ${order.order_no || order.order_id}`);
419
- if (url) lines.push(`收银台:${url}`);
420
- lines.push('支付成功后证据才会进入案卷。');
421
- }
422
415
  return io.ok({ case_id: caseId, ...out }, lines.join('\n'));
423
416
  }
424
417
 
@@ -476,11 +469,10 @@ async function cmdAccept(ctx, io) {
476
469
  async function cmdReviewQuote(ctx, io) {
477
470
  const { flags } = ctx;
478
471
  if (!flags.task) return io.fail(2, 'missing_task', '缺少 --task <task-id>');
479
- const ev = evidenceInputs(flags, io);
480
- if (ev.fail !== undefined) return ev.fail;
472
+ if (flags.evidence.length) return io.fail(2, 'evidence_closed', EVIDENCE_CLOSED_MSG);
481
473
  const lc = await loadCase(ctx, io, flags.task);
482
474
  if (lc.fail !== undefined) return lc.fail;
483
- const q = await libraReviewQuote(ctx.baseUrl, ctx.credOpts, lc.caseView.id, { evidence: ev.evidence }, { fetchImpl: ctx.fetchImpl });
475
+ const q = await libraReviewQuote(ctx.baseUrl, ctx.credOpts, lc.caseView.id, {}, { fetchImpl: ctx.fetchImpl });
484
476
  saveQuote(ctx.credOpts, q.quote_id, {
485
477
  kind: 'review', task_id: flags.task, case_id: lc.caseView.id, points: flags.points || null,
486
478
  total_cents: q.total_cents, expires_at: q.expires_at,
@@ -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
  }
@@ -155,7 +158,7 @@ export function describeNegotiation(ctx, view, taskId) {
155
158
  } else {
156
159
  const c = n.closed || {};
157
160
  lines.push(` 状态:已结束(${KIND_WORDS[c.kind] || n.status})`);
158
- if (c.refund_pct != null) lines.push(` 退还比例:${c.refund_pct}%${n.amounts ? `(退还 ${money(view, n.amounts.refund_cents)},结算给接单方 ${money(view, n.amounts.release_cents)})` : ''}`);
161
+ if (c.refund_pct != null) lines.push(` 退还比例:${c.refund_pct}%${n.amounts ? `(退还 ${money(view, n.amounts.refund_cents)},结算给接单方 ${money(view, n.amounts.release_cents)}${n.amounts.release_net_cents != null ? `,扣掉平台费用和同款分成后接单方预计到手 ${money(view, n.amounts.release_net_cents)}` : ''})` : ''}`);
159
162
  if (c.kind === 'court') lines.push(' 已转入 AI 法庭裁决,请用 anytowork libra status 看进展。');
160
163
  }
161
164
  if ((n.entries || []).length) lines.push(' 记录:', ...n.entries.map((en) => entryLine(view, en)));