@whyshu/argent-engine 0.7.3 → 0.7.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/MANIFEST.json +39 -28
- package/engine/VERSION +1 -1
- package/engine/scripts/appointment-create.mjs +34 -0
- package/engine/scripts/appointment-stop.mjs +18 -0
- package/engine/scripts/appointment-worker.mjs +96 -0
- package/engine/scripts/appointment_grab.py +327 -0
- package/engine/scripts/argent-runner.mjs +223 -0
- package/engine/scripts/argent-setup-check.mjs +3 -3
- package/engine/scripts/capability-packs.lock.json +33 -39
- package/engine/scripts/check-agent-catalog.mjs +1 -1
- package/engine/scripts/check-config-catalog.mjs +0 -1
- package/engine/scripts/collect-evidence.mjs +2 -5
- package/engine/scripts/daily-report.mjs +315 -12
- package/engine/scripts/dsh-upgrade.mjs +18 -5
- package/engine/scripts/flow-requirements.json +5 -7
- package/engine/scripts/flow-timers.sh +1 -1
- package/engine/scripts/flow-trigger.sh +1 -1
- package/engine/scripts/gen-preset-rows.mjs +20 -0
- package/engine/scripts/invoice_stageb.py +20 -1
- package/engine/scripts/lib/agent-install.mjs +10 -1
- package/engine/scripts/lib/daily-report-core.mjs +124 -16
- package/engine/scripts/lib/dsh-versions.mjs +17 -1
- package/engine/scripts/lib/ml-ads-core.mjs +204 -0
- package/engine/scripts/lib/plugin-packages.mjs +1 -0
- package/engine/scripts/ml-ads.mjs +181 -0
- package/engine/scripts/ml-oauth-setup.mjs +1 -1
- package/engine/scripts/p2-report-run.mjs +102 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +34 -9
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +44 -0
- package/engine/scripts/patches/verify-hardening.mjs +4 -1
- package/engine/scripts/patches/verify-turn-outside-replace.mjs +64 -0
- package/engine/scripts/poll-fulfillment.sh +1 -1
- package/engine/scripts/profile-doctor.mjs +1 -1
- package/engine/scripts/python-runtime.sh +16 -4
- package/engine/scripts/release-argent.sh +8 -7
- package/engine/scripts/session-load-check.mjs +105 -0
- package/engine/scripts/sync-scripts.sh +1 -1
- package/engine/scripts/xzh-browser.sh +37 -0
- package/engine/scripts/xzh_browser.py +49 -0
- package/engine/skills/ml-full-appointment/SKILL.md +98 -0
- package/engine/version.mjs +1 -1
- package/package.json +1 -1
- package/engine/scripts/drill-clean-machine.sh +0 -315
- package/engine/scripts/install-internal-role.sh +0 -190
- package/engine/scripts/install-one-shot.mjs +0 -665
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* node daily-report.mjs --force # 跳过时间门控立即推送(成功后写幂等标记)
|
|
16
16
|
* node daily-report.mjs --force --no-mark # 测试/验证:强制推送但不写幂等标记(不挡当晚定时推送)
|
|
17
17
|
* node daily-report.mjs --date 2026-08-20 # 指定日报日期(默认各店本地昨日)
|
|
18
|
+
* node daily-report.mjs --date 2026-08-20 --as-of 2026-08-21T12:00:00Z # 回放:把"现在"也钉住(单日窗口验收用)
|
|
18
19
|
* node daily-report.mjs --env <env 文件路径> # 默认 ~/.dsh/argent/daily-report.env
|
|
19
20
|
* node daily-report.mjs --mock <fixture.json> # 开发自测:跳过联网,仅渲染/打印
|
|
20
21
|
*
|
|
@@ -39,8 +40,11 @@ import {
|
|
|
39
40
|
aggregateOrders, reportWindows, windowInfo, capturedAtInfo, captureNoteLines,
|
|
40
41
|
matchStores, storeKey,
|
|
41
42
|
localDateStr, tzOffsetMs, startOfLocalDayUTC, localHour,
|
|
42
|
-
apiGet as coreApiGet, fetchOrders as coreFetchOrders, refreshStoreToken as coreRefreshStoreToken,
|
|
43
|
+
apiGet as coreApiGet, fetchOrders as coreFetchOrders, fetchOrdersWithMeta as coreFetchOrdersWithMeta, refreshStoreToken as coreRefreshStoreToken,
|
|
43
44
|
} from './lib/daily-report-core.mjs'
|
|
45
|
+
// 广告(Product Ads)只读取数/聚合/告警:与 `scripts/ml-ads.mjs` 共用同一份实现
|
|
46
|
+
// (⛔ 不在两处各写一份口径;2026-10-17 新增,默认关,客户开广告权限后再启用 `ads.summary`)
|
|
47
|
+
import { fetchAdsReport, adsCardLines } from './lib/ml-ads-core.mjs'
|
|
44
48
|
|
|
45
49
|
// 逐字转出(既有调用方/单测 import 路径不变;同一绑定 ⇒ `M.aggregateOrders === core.aggregateOrders`)
|
|
46
50
|
export { aggregateOrders, localDateStr, tzOffsetMs, startOfLocalDayUTC, localHour, loadEnvFile }
|
|
@@ -83,7 +87,15 @@ const FLAG_FORCE = args.includes('--force')
|
|
|
83
87
|
// KB-074:测试/验证用——推送成功后不写幂等标记(手动 --force 不挡当晚定时推送)
|
|
84
88
|
const FLAG_NO_MARK = args.includes('--no-mark')
|
|
85
89
|
const FLAG_JSON = args.includes('--json') // KB-063:只输出日报卡片 JSON(BOT 回复卡片用),不推送
|
|
90
|
+
// 防呆(2026-10-18 真实事故):`--dry` 不是合法 flag(正确是 `--dry-run`),此前被静默忽略 ⇒ 回放时误推送。
|
|
91
|
+
if (args.includes('--dry')) {
|
|
92
|
+
console.error('✗ 未知参数 --dry:dry-run 的正确写法是 `--dry-run`;本次不执行(避免误推送)')
|
|
93
|
+
process.exit(2)
|
|
94
|
+
}
|
|
86
95
|
const ARG_DATE = argVal('--date')
|
|
96
|
+
// 回放验收(2026-10-18):`--date` 只移动报告日,`todayStart` 仍取"今天" ⇒ 历史重放会跨多日。
|
|
97
|
+
// `--as-of` 把"现在"也钉住,窗口才真正是那一天(只影响窗口/抓取时刻,不改变真实推送时刻判定——配合 --force)。
|
|
98
|
+
const ARG_ASOF = argVal('--as-of')
|
|
87
99
|
const FLAG_LIST_SECTIONS = args.includes('--list-sections')
|
|
88
100
|
const FLAG_LIST_STORES = args.includes('--list-stores')
|
|
89
101
|
const ARG_STORE = (argVal('--store') || '').split(',').map((x) => x.trim()).filter(Boolean)
|
|
@@ -421,6 +433,16 @@ export const SECTIONS = [
|
|
|
421
433
|
return out
|
|
422
434
|
},
|
|
423
435
|
},
|
|
436
|
+
{
|
|
437
|
+
id: 'ads.summary', group: 'ads', title: '广告(Product Ads)', needs: ['ads'],
|
|
438
|
+
cost: { requests: '按活动数(分页累加)' }, cadence: 'daily', default: false,
|
|
439
|
+
render: ({ d, store }) => {
|
|
440
|
+
const ads = d && d.ads
|
|
441
|
+
const errs = (d && d.__errors) || {}
|
|
442
|
+
if (!ads) return errs.ads ? [`**📣 广告**:未取到(${errs.ads})`] : null
|
|
443
|
+
return adsCardLines(ads, { currency: SITE_CURRENCY[fallbackSite(store.site)] || '', alertOpts: {} })
|
|
444
|
+
},
|
|
445
|
+
},
|
|
424
446
|
{
|
|
425
447
|
id: 'ops.restock', group: 'ops', title: '补货提醒', needs: ['orders', 'items'], cost: { requests: 0 },
|
|
426
448
|
cadence: 'daily', default: true,
|
|
@@ -587,6 +609,162 @@ export function buildOverviewBlocks(dateLabel, items) {
|
|
|
587
609
|
return { head: lines, risk: [], groups: [] }
|
|
588
610
|
}
|
|
589
611
|
|
|
612
|
+
// ── 表格化日报(飞书表格式摘要卡 + ERP 完整日报页)────────
|
|
613
|
+
const REPORT_METRIC_DEFS = [
|
|
614
|
+
{ key: 'gmv', label: 'GMV', kind: 'money' },
|
|
615
|
+
{ key: 'orders', label: '订单', kind: 'int' },
|
|
616
|
+
{ key: 'units', label: '件数', kind: 'int' },
|
|
617
|
+
{ key: 'avg', label: '客单价', kind: 'int' },
|
|
618
|
+
{ key: 'cancelled', label: '取消', kind: 'int' },
|
|
619
|
+
]
|
|
620
|
+
|
|
621
|
+
function fmtNum(n) { return n == null || !Number.isFinite(Number(n)) ? '—' : Math.round(Number(n)).toLocaleString('en-US') }
|
|
622
|
+
function fmtMoney(code, n) { return n == null || !Number.isFinite(Number(n)) ? '—' : `${code} ${fmtNum(n)}` }
|
|
623
|
+
function fmtDelta(cur, prev) {
|
|
624
|
+
if (cur == null || prev == null || !Number.isFinite(Number(cur)) || !Number.isFinite(Number(prev)) || Number(prev) === 0) return '—'
|
|
625
|
+
const p = Math.round(((Number(cur) - Number(prev)) / Number(prev)) * 100)
|
|
626
|
+
return `${p > 0 ? '↗ +' : p < 0 ? '↘ ' : '→ '}${p}%`
|
|
627
|
+
}
|
|
628
|
+
function metricValue(store, key, src) {
|
|
629
|
+
const m = store.metrics && store.metrics[key]
|
|
630
|
+
if (!m) return null
|
|
631
|
+
return m[src] == null ? null : m[src]
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** 把当日采集结果整理成“所有日报数据”的结构化表(ERP 页与飞书表卡共用)。 */
|
|
635
|
+
export function buildReportPayload({ dateLabel, blocksList = [], failed = [] }) {
|
|
636
|
+
const stores = blocksList.map(({ store, r, blocks }) => {
|
|
637
|
+
const cur = r.summary || {}
|
|
638
|
+
const cmp = r.comparisons || {}
|
|
639
|
+
const comp = (key, metric) => {
|
|
640
|
+
const c = cmp[metric] || {}
|
|
641
|
+
return c[key] == null ? null : c[key]
|
|
642
|
+
}
|
|
643
|
+
const metrics = {
|
|
644
|
+
gmv: { current: cur.gmv, prevDay: comp('gmv', 'prevDay'), prevWeek: comp('gmv', 'prevWeek'), prevMonth: comp('gmv', 'prevMonth') },
|
|
645
|
+
orders: { current: cur.orders, prevDay: comp('orders', 'prevDay'), prevWeek: comp('orders', 'prevWeek'), prevMonth: comp('orders', 'prevMonth') },
|
|
646
|
+
units: { current: cur.units, prevDay: comp('units', 'prevDay'), prevWeek: comp('units', 'prevWeek'), prevMonth: comp('units', 'prevMonth') },
|
|
647
|
+
avg: { current: cur.avg, prevDay: comp('avg', 'prevDay'), prevWeek: comp('avg', 'prevWeek'), prevMonth: comp('avg', 'prevMonth') },
|
|
648
|
+
cancelled: { current: cur.cancelled, prevDay: comp('cancelled', 'prevDay'), prevWeek: comp('cancelled', 'prevWeek'), prevMonth: comp('cancelled', 'prevMonth') },
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
name: store.name,
|
|
652
|
+
site: store.site,
|
|
653
|
+
currency: SITE_CURRENCY[store.site] || 'USD',
|
|
654
|
+
summary: cur,
|
|
655
|
+
comparisons: cmp,
|
|
656
|
+
metrics,
|
|
657
|
+
top: (r.top || []).map((x) => ({
|
|
658
|
+
sku: x.sku || '', title: x.title || '', units7: x.units7 ?? null, gmv7: x.gmv7 ?? null,
|
|
659
|
+
avail: x.avail ?? null, sold: x.sold ?? null, daysLeft: x.days ?? x.daysLeft ?? null,
|
|
660
|
+
})),
|
|
661
|
+
alerts: (r.alerts || []).map((x) => ({
|
|
662
|
+
sku: x.sku || '', title: x.title || '', avail: x.avail ?? null, units7: x.units7 ?? null,
|
|
663
|
+
daysLeft: x.days ?? x.daysLeft ?? null,
|
|
664
|
+
})),
|
|
665
|
+
risk: blocks && blocks.risk ? blocks.risk : [],
|
|
666
|
+
sections: blocks
|
|
667
|
+
? [
|
|
668
|
+
...(blocks.head && blocks.head.length ? [{ group: 'head', label: '店铺', lines: blocks.head }] : []),
|
|
669
|
+
...(blocks.risk && blocks.risk.length ? [{ group: 'risk', label: '风险与待办', lines: blocks.risk }] : []),
|
|
670
|
+
...(blocks.groups || []).map((g) => ({ group: g.group, label: GROUP_LABEL[g.group] || g.group, lines: g.lines })),
|
|
671
|
+
]
|
|
672
|
+
: [],
|
|
673
|
+
error: r.error || '',
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
const sumBy = (key, src) => stores.reduce((a, x) => {
|
|
677
|
+
const v = metricValue(x, key, src)
|
|
678
|
+
return v == null ? a : a + Number(v)
|
|
679
|
+
}, 0)
|
|
680
|
+
const totals = {}
|
|
681
|
+
for (const def of REPORT_METRIC_DEFS) {
|
|
682
|
+
if (def.key === 'avg') {
|
|
683
|
+
const calc = (src) => {
|
|
684
|
+
const orders = sumBy('orders', src)
|
|
685
|
+
return orders > 0 ? sumBy('gmv', src) / orders : null
|
|
686
|
+
}
|
|
687
|
+
totals.avg = { current: calc('current'), prevDay: calc('prevDay'), prevWeek: calc('prevWeek'), prevMonth: calc('prevMonth') }
|
|
688
|
+
} else {
|
|
689
|
+
totals[def.key] = {
|
|
690
|
+
current: sumBy(def.key, 'current'), prevDay: sumBy(def.key, 'prevDay'),
|
|
691
|
+
prevWeek: sumBy(def.key, 'prevWeek'), prevMonth: sumBy(def.key, 'prevMonth'),
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
date: dateLabel,
|
|
697
|
+
generatedAt: new Date().toISOString(),
|
|
698
|
+
stores,
|
|
699
|
+
totals,
|
|
700
|
+
failed: failed.map((f) => ({ name: f.store.name, error: f.r.error || '未知错误' })),
|
|
701
|
+
labels: {
|
|
702
|
+
prevDay: (stores[0] && stores[0].comparisons && stores[0].comparisons.labels && stores[0].comparisons.labels.prevDay) || '前一日',
|
|
703
|
+
prevWeek: (stores[0] && stores[0].comparisons && stores[0].comparisons.labels && stores[0].comparisons.labels.prevWeek) || '上周同期',
|
|
704
|
+
prevMonth: (stores[0] && stores[0].comparisons && stores[0].comparisons.labels && stores[0].comparisons.labels.prevMonth) || '上月同期',
|
|
705
|
+
},
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** 飞书表格式摘要卡:跨店表 + 核心指标对比表 + “查看完整日报”按钮。 */
|
|
710
|
+
export function buildTableCard(payload) {
|
|
711
|
+
const stores = payload.stores || []
|
|
712
|
+
const overviewColumns = [
|
|
713
|
+
{ name: 'store', display_name: '店铺', data_type: 'text', width: '120px', horizontal_align: 'left' },
|
|
714
|
+
{ name: 'gmv', display_name: '昨日 GMV', data_type: 'text', width: '100px' },
|
|
715
|
+
{ name: 'orders', display_name: '订单', data_type: 'text', width: '80px' },
|
|
716
|
+
{ name: 'units', display_name: '件数', data_type: 'text', width: '80px' },
|
|
717
|
+
{ name: 'avg', display_name: '客单价', data_type: 'text', width: '80px' },
|
|
718
|
+
{ name: 'cancel', display_name: '取消', data_type: 'text', width: '80px' },
|
|
719
|
+
{ name: 'd1', display_name: '较前日', data_type: 'text', width: '80px' },
|
|
720
|
+
{ name: 'd7', display_name: '较上周', data_type: 'text', width: '80px' },
|
|
721
|
+
{ name: 'd30', display_name: '较上月', data_type: 'text', width: '80px' },
|
|
722
|
+
]
|
|
723
|
+
const overviewRows = stores.slice(0, 10).map((x) => ({
|
|
724
|
+
store: x.name,
|
|
725
|
+
gmv: fmtMoney(x.currency, metricValue(x, 'gmv', 'current')),
|
|
726
|
+
orders: fmtNum(metricValue(x, 'orders', 'current')),
|
|
727
|
+
units: fmtNum(metricValue(x, 'units', 'current')),
|
|
728
|
+
avg: fmtNum(metricValue(x, 'avg', 'current')),
|
|
729
|
+
cancel: fmtNum(metricValue(x, 'cancelled', 'current')),
|
|
730
|
+
d1: fmtDelta(metricValue(x, 'gmv', 'current'), metricValue(x, 'gmv', 'prevDay')),
|
|
731
|
+
d7: fmtDelta(metricValue(x, 'gmv', 'current'), metricValue(x, 'gmv', 'prevWeek')),
|
|
732
|
+
d30: fmtDelta(metricValue(x, 'gmv', 'current'), metricValue(x, 'gmv', 'prevMonth')),
|
|
733
|
+
}))
|
|
734
|
+
const totalColumns = [
|
|
735
|
+
{ name: 'k', display_name: '指标', data_type: 'text', width: '100px', horizontal_align: 'left' },
|
|
736
|
+
{ name: 'cur', display_name: '昨日', data_type: 'text', width: '110px' },
|
|
737
|
+
{ name: 'd1', display_name: '前一日', data_type: 'text', width: '110px' },
|
|
738
|
+
{ name: 'd7', display_name: '上周同期', data_type: 'text', width: '110px' },
|
|
739
|
+
{ name: 'd30', display_name: '上月同期', data_type: 'text', width: '110px' },
|
|
740
|
+
]
|
|
741
|
+
// GMV 不能跨币种直接相加(MXN/BRL/ARS 混在一起是错数);总览只放计数指标,
|
|
742
|
+
// 各币种 GMV 已经在跨店表内逐店显示,完整明细看 ERP。
|
|
743
|
+
const totalRows = [
|
|
744
|
+
{ k: '订单', cur: fmtNum(payload.totals?.orders?.current), d1: fmtNum(payload.totals?.orders?.prevDay), d7: fmtNum(payload.totals?.orders?.prevWeek), d30: fmtNum(payload.totals?.orders?.prevMonth) },
|
|
745
|
+
{ k: '件数', cur: fmtNum(payload.totals?.units?.current), d1: fmtNum(payload.totals?.units?.prevDay), d7: fmtNum(payload.totals?.units?.prevWeek), d30: fmtNum(payload.totals?.units?.prevMonth) },
|
|
746
|
+
{ k: '取消', cur: fmtNum(payload.totals?.cancelled?.current), d1: fmtNum(payload.totals?.cancelled?.prevDay), d7: fmtNum(payload.totals?.cancelled?.prevWeek), d30: fmtNum(payload.totals?.cancelled?.prevMonth) },
|
|
747
|
+
]
|
|
748
|
+
const url = process.env.ML_ERP_REPORT_URL || 'https://whyshu.com/admin/argent/#/reports'
|
|
749
|
+
const elements = [
|
|
750
|
+
{ tag: 'markdown', content: `**跨店总览** · 昨日 / 前一日 / 上周同期 / 上月同期对比(完整数据点下方按钮)` },
|
|
751
|
+
{ tag: 'table', page_size: 10, row_height: 'low', freeze_first_column: true, header_style: { bold: true, background_style: 'grey' }, columns: overviewColumns, rows: overviewRows },
|
|
752
|
+
{ tag: 'markdown', content: `**核心指标对比**` },
|
|
753
|
+
{ tag: 'table', page_size: 5, row_height: 'low', header_style: { bold: true, background_style: 'grey' }, columns: totalColumns, rows: totalRows },
|
|
754
|
+
]
|
|
755
|
+
if ((payload.failed || []).length) elements.push({ tag: 'markdown', content: `⚠️ 数据缺失:${payload.failed.map((x) => `${x.name}(${x.error})`).join(';')}` })
|
|
756
|
+
elements.push({ tag: 'button', text: { tag: 'plain_text', content: '查看完整日报' }, type: 'primary_filled', width: 'fill', behaviors: [{ type: 'open_url', default_url: url }] })
|
|
757
|
+
return {
|
|
758
|
+
msg_type: 'interactive',
|
|
759
|
+
card: {
|
|
760
|
+
schema: '2.0',
|
|
761
|
+
config: { wide_screen_mode: true },
|
|
762
|
+
header: { template: (payload.failed || []).length ? 'yellow' : 'blue', title: { tag: 'plain_text', content: `📊 美客多日报 · ${payload.date}|表格摘要` } },
|
|
763
|
+
body: { elements },
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
590
768
|
// 超长内容分段(飞书卡片 markdown 元素单段上限保守取 3000 字符)
|
|
591
769
|
export function chunkText(text, size = 3000) {
|
|
592
770
|
const out = []
|
|
@@ -618,6 +796,16 @@ export function buildCardPayload(title, sections) {
|
|
|
618
796
|
}
|
|
619
797
|
}
|
|
620
798
|
|
|
799
|
+
/** 订单采集不完整时的卡片警告(成功/老快照 ⇒ null,卡片字节不变)。 */
|
|
800
|
+
export function orderIncompleteWarning(r) {
|
|
801
|
+
const of = r && r.raw && r.raw.orderFetch
|
|
802
|
+
if (!of || of.complete !== false) return null
|
|
803
|
+
const parts = []
|
|
804
|
+
if (of.week && of.week.got < of.week.total) parts.push(`本周窗口 ${of.week.got}/${of.week.total}`)
|
|
805
|
+
if (of.prev && of.prev.got < of.prev.total) parts.push(`环比窗口 ${of.prev.got}/${of.prev.total}`)
|
|
806
|
+
return `⚠️ **订单采集不完整**(${parts.join(',') || '分页未取全'})—— 昨日与榜单偏低,勿直接采信`
|
|
807
|
+
}
|
|
808
|
+
|
|
621
809
|
// ── 网络层:共用取数实现(本文件只注入 API 基址;实现见 lib/daily-report-core.mjs)──
|
|
622
810
|
// `apiGet` / `fetchOrders` / `refreshStoreToken` 的**判断**(429 退避、token 复用 15min 阈值、
|
|
623
811
|
// 轮换写回)全在共用实现里 —— 两个 CLI 一份实现,⛔ 不再有第二份。这里只做一件事:
|
|
@@ -634,6 +822,11 @@ async function fetchOrders(token, sellerId, fromISO, toISO) {
|
|
|
634
822
|
return coreFetchOrders(token, sellerId, fromISO, toISO, { base: apiBase() })
|
|
635
823
|
}
|
|
636
824
|
|
|
825
|
+
// P0(2026-10-18):日报要拿到「订单采集是否完整」——超限窗口按整点二分取全量,不再静默截断。
|
|
826
|
+
async function fetchOrdersWithMeta(token, sellerId, fromISO, toISO) {
|
|
827
|
+
return coreFetchOrdersWithMeta(token, sellerId, fromISO, toISO, { base: apiBase() })
|
|
828
|
+
}
|
|
829
|
+
|
|
637
830
|
async function fetchItems(token, userId) {
|
|
638
831
|
const map = {}
|
|
639
832
|
const ids = []
|
|
@@ -793,6 +986,29 @@ function pushLarkCard(card, chatId, profile) {
|
|
|
793
986
|
}
|
|
794
987
|
}
|
|
795
988
|
|
|
989
|
+
/** 上传结构化日报到 ERP(bridge secret;失败不阻断群推送,ERP 页显示上一份)。 */
|
|
990
|
+
async function publishReportPayload(payload, env) {
|
|
991
|
+
try {
|
|
992
|
+
const secret = process.env.DAILY_REPORT_BRIDGE_SECRET || (() => {
|
|
993
|
+
try { return readFileSync(join(DATA_DIR, 'appointment-bridge.secret'), 'utf8').trim() } catch { return '' }
|
|
994
|
+
})()
|
|
995
|
+
if (!secret) return false
|
|
996
|
+
const base = (process.env.ARGENT_API_BASE || 'https://whyshu.com/api/argent/v1').replace(/\/+$/, '')
|
|
997
|
+
const appId = process.env.ARGENT_TENANT_APP_ID || 'ly-0001'
|
|
998
|
+
const res = await fetch(`${base}/tenants/${appId}/reports/daily/bridge`, {
|
|
999
|
+
method: 'POST',
|
|
1000
|
+
headers: { 'x-appointment-secret': secret, 'content-type': 'application/json' },
|
|
1001
|
+
body: JSON.stringify({ reportDate: payload.date, payload }),
|
|
1002
|
+
signal: AbortSignal.timeout(30000),
|
|
1003
|
+
})
|
|
1004
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
1005
|
+
return true
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
console.error(`[daily-report] ⚠️ ERP 日报上传失败(群推送继续):${String(e && e.message || e).slice(0, 180)}`)
|
|
1008
|
+
return false
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
796
1012
|
async function pushCards(env, cards, overview) {
|
|
797
1013
|
const cfg = larkConfig()
|
|
798
1014
|
const chatId = pick(cfg, env, 'mlDailyChatId', 'ML_DAILY_CHAT_ID', ML_DAILY_CHAT_ID)
|
|
@@ -983,6 +1199,23 @@ async function collectPriceToWin(token, ids, gapMs = 100) {
|
|
|
983
1199
|
}
|
|
984
1200
|
|
|
985
1201
|
/** 按"已启用分区的 needs 并集"取数;返回 { reputation, claims, questions, sla } 中真正需要的项。 */
|
|
1202
|
+
// ── 广告采集(Product Ads)────────────────────────────────
|
|
1203
|
+
// 窗口:**近 7 个完整自然日**(站点本地,截止到昨天),与 `ml-ads.mjs` 默认窗口一致。
|
|
1204
|
+
// 结构:广告主 → campaign 逐页累加(`metrics_summary` 只汇总返回条目,⛔ 不能当总额)。
|
|
1205
|
+
async function collectAds(env, store, token) {
|
|
1206
|
+
const site = fallbackSite(store.site)
|
|
1207
|
+
const tz = SITE_TZ[site]
|
|
1208
|
+
const now = new Date()
|
|
1209
|
+
const todayStart = startOfLocalDayUTC(tz, now)
|
|
1210
|
+
const toMs = todayStart - 86400000
|
|
1211
|
+
const fromMs = toMs - 6 * 86400000
|
|
1212
|
+
return fetchAdsReport({
|
|
1213
|
+
apiGet, token, site,
|
|
1214
|
+
dateFrom: localDateStr(tz, new Date(fromMs)),
|
|
1215
|
+
dateTo: localDateStr(tz, new Date(toMs)),
|
|
1216
|
+
})
|
|
1217
|
+
}
|
|
1218
|
+
|
|
986
1219
|
async function collectExtras(env, store, token, need, ctx) {
|
|
987
1220
|
const cfg = larkConfig()
|
|
988
1221
|
const want = (n) => need.has(n)
|
|
@@ -1005,6 +1238,7 @@ async function collectExtras(env, store, token, need, ctx) {
|
|
|
1005
1238
|
}
|
|
1006
1239
|
if (want('priceToWin')) jobs.push(run('price', collectPriceToWin(token, ranked.slice(0, topN))))
|
|
1007
1240
|
}
|
|
1241
|
+
if (want('ads')) jobs.push(run('ads', collectAds(env, store, token)))
|
|
1008
1242
|
if (want('shipments')) {
|
|
1009
1243
|
const windowDays = Number(pick(cfg, env, 'mlSlaWindowDays', 'ML_SLA_WINDOW_DAYS', 2))
|
|
1010
1244
|
const cap = Number(pick(cfg, env, 'mlSlaMaxChecks', 'ML_SLA_MAX_CHECKS', 300))
|
|
@@ -1014,19 +1248,72 @@ async function collectExtras(env, store, token, need, ctx) {
|
|
|
1014
1248
|
return out
|
|
1015
1249
|
}
|
|
1016
1250
|
|
|
1251
|
+
/** 单日汇总(用于前一日/上周同期/上月同期对比;只拉一个自然日,成本可控)。 */
|
|
1252
|
+
async function daySummary(token, userId, startMs, itemById, site) {
|
|
1253
|
+
const dayMs = 86400000
|
|
1254
|
+
const f = await fetchOrdersWithMeta(token, userId, new Date(startMs).toISOString(), new Date(startMs + dayMs).toISOString())
|
|
1255
|
+
const { summary } = aggregateOrders(f.orders, itemById, { weekStartMs: startMs, reportStartMs: startMs, todayStartMs: startMs + dayMs }, site)
|
|
1256
|
+
return { ...summary, complete: Boolean(f.complete), got: f.got ?? null, total: f.total ?? null }
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/** 报告日往前一个月的“同一天”(站点本地日期;优先返回该日期 12:00 UTC 的 Date 供 startOfLocalDayUTC 使用)。 */
|
|
1260
|
+
function sameDayPrevMonth(tz, reportDate) {
|
|
1261
|
+
const [y, m, d] = localDateStr(tz, reportDate).split('-').map(Number)
|
|
1262
|
+
const first = new Date(Date.UTC(y, m - 2, 1, 12)) // 上个月 1 日
|
|
1263
|
+
const days = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate()
|
|
1264
|
+
first.setUTCDate(Math.min(d, days))
|
|
1265
|
+
return first
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1017
1268
|
async function collectStore(env, store, now, reportDate, opts = {}) {
|
|
1018
1269
|
// 窗口计算(站点本地自然日 + 近 7 日)与抓取时刻:共用实现(对账 CLI 用同一份口径)
|
|
1019
1270
|
const { site, tz, weekStart, reportStart, todayStart, prevStart } =
|
|
1020
1271
|
reportWindows(store.site, reportDate, now, { salesWindowDays: env.SALES_WINDOW_DAYS })
|
|
1021
1272
|
const token = await refreshStoreToken(env, store)
|
|
1022
1273
|
const needPrev = (opts.need || new Set()).has('ordersPrev')
|
|
1023
|
-
const [
|
|
1024
|
-
|
|
1274
|
+
const [weekFetch, itemsRes, prevFetch] = await Promise.all([
|
|
1275
|
+
fetchOrdersWithMeta(token, store.user_id, new Date(weekStart).toISOString(), new Date(todayStart).toISOString()),
|
|
1025
1276
|
fetchItems(token, store.user_id),
|
|
1026
|
-
needPrev ?
|
|
1277
|
+
needPrev ? fetchOrdersWithMeta(token, store.user_id, new Date(prevStart).toISOString(), new Date(weekStart).toISOString()) : Promise.resolve(null),
|
|
1027
1278
|
])
|
|
1279
|
+
const orders = weekFetch.orders
|
|
1280
|
+
const prevOrders = prevFetch ? prevFetch.orders : null
|
|
1281
|
+
const fetchMetaOf = (m) => (m ? { total: m.total, got: m.got, complete: m.complete, windows: m.windows.length, reason: m.reason } : null)
|
|
1282
|
+
const orderFetch = { complete: weekFetch.complete && (!prevFetch || prevFetch.complete), week: fetchMetaOf(weekFetch), prev: fetchMetaOf(prevFetch) }
|
|
1283
|
+
if (!orderFetch.complete) {
|
|
1284
|
+
const parts = []
|
|
1285
|
+
if (!weekFetch.complete) parts.push(`本周窗口 ${weekFetch.got}/${weekFetch.total}(${weekFetch.reason})`)
|
|
1286
|
+
if (prevFetch && !prevFetch.complete) parts.push(`环比窗口 ${prevFetch.got}/${prevFetch.total}(${prevFetch.reason})`)
|
|
1287
|
+
console.error(`[daily-report] ⚠️ ${store.name} 订单采集不完整:${parts.join(',')} —— 昨日与榜单会偏低`)
|
|
1288
|
+
}
|
|
1028
1289
|
const itemById = itemsRes.map // fetchItems 现在返回 {map,total,failed}
|
|
1029
1290
|
const windows = { weekStartMs: weekStart, reportStartMs: reportStart, todayStartMs: todayStart }
|
|
1291
|
+
// 表格卡/ERP 日报页需要:前一日、上周同期(-7 天)、上月同期(上个月同一天)。
|
|
1292
|
+
// 只增加两个单日窗口请求;失败不阻断主流程(缺对比显示 —)。
|
|
1293
|
+
let comparisons = null
|
|
1294
|
+
if (opts.comparisons !== false && process.env.ML_DAILY_COMPARISONS !== '0') {
|
|
1295
|
+
const dayMs = 86400000
|
|
1296
|
+
const prevDayStart = reportStart - dayMs
|
|
1297
|
+
const prevWeekStart = startOfLocalDayUTC(tz, new Date(reportDate.getTime() - 7 * dayMs))
|
|
1298
|
+
const prevMonthStart = startOfLocalDayUTC(tz, sameDayPrevMonth(tz, reportDate))
|
|
1299
|
+
try {
|
|
1300
|
+
const [prevDay, prevWeek, prevMonth] = await Promise.all([
|
|
1301
|
+
daySummary(token, store.user_id, prevDayStart, itemById, site),
|
|
1302
|
+
daySummary(token, store.user_id, prevWeekStart, itemById, site),
|
|
1303
|
+
daySummary(token, store.user_id, prevMonthStart, itemById, site),
|
|
1304
|
+
])
|
|
1305
|
+
comparisons = {
|
|
1306
|
+
prevDay, prevWeek, prevMonth,
|
|
1307
|
+
labels: {
|
|
1308
|
+
prevDay: localDateStr(tz, new Date(prevDayStart)),
|
|
1309
|
+
prevWeek: localDateStr(tz, new Date(prevWeekStart)),
|
|
1310
|
+
prevMonth: localDateStr(tz, new Date(prevMonthStart)),
|
|
1311
|
+
},
|
|
1312
|
+
}
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
comparisons = { error: String(e && e.message || e).slice(0, 120) }
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1030
1317
|
const { summary, skus, daily, buyers } = aggregateOrders(orders, itemById, windows, site)
|
|
1031
1318
|
let prevSkus = null
|
|
1032
1319
|
if (prevOrders) {
|
|
@@ -1060,6 +1347,7 @@ async function collectStore(env, store, now, reportDate, opts = {}) {
|
|
|
1060
1347
|
alerts,
|
|
1061
1348
|
threshold,
|
|
1062
1349
|
prev,
|
|
1350
|
+
comparisons,
|
|
1063
1351
|
daily,
|
|
1064
1352
|
buyers,
|
|
1065
1353
|
prevSkus,
|
|
@@ -1071,7 +1359,7 @@ async function collectStore(env, store, now, reportDate, opts = {}) {
|
|
|
1071
1359
|
})),
|
|
1072
1360
|
itemLogistics: Object.fromEntries(Object.entries(itemById).map(([id, it]) => [id, (it && it.shipping && it.shipping.logistic_type) || ''])),
|
|
1073
1361
|
data,
|
|
1074
|
-
raw: { skuCount: skus.length, orderCount: orders.length },
|
|
1362
|
+
raw: { skuCount: skus.length, orderCount: orders.length, orderFetch },
|
|
1075
1363
|
}
|
|
1076
1364
|
}
|
|
1077
1365
|
|
|
@@ -1126,7 +1414,8 @@ async function main() {
|
|
|
1126
1414
|
console.error(`[daily-report] 启用分区 ${enabled.size}/${SECTIONS.length}:${[...enabled].join(', ')}`
|
|
1127
1415
|
+ (cadence.cached.size ? `|周更(用缓存,不取数):${[...cadence.cached].join(', ')}` : '')
|
|
1128
1416
|
+ `|数据需求:${[...need].join(', ')}`)
|
|
1129
|
-
const now = new Date()
|
|
1417
|
+
const now = ARG_ASOF ? new Date(ARG_ASOF) : new Date()
|
|
1418
|
+
if (ARG_ASOF && Number.isNaN(now.getTime())) { console.error(`✗ --as-of 解析失败:${ARG_ASOF}`); return 1 }
|
|
1130
1419
|
const reportTime = pick(cfg, env, 'mlDailyTime', 'REPORT_TIME', '08:00')
|
|
1131
1420
|
|
|
1132
1421
|
if (MOCK_FILE) {
|
|
@@ -1188,7 +1477,7 @@ async function main() {
|
|
|
1188
1477
|
try {
|
|
1189
1478
|
console.error(`[daily-report] 采集 ${store.name}(${store.site})...`) // stderr:--json 模式下 stdout 保持纯 JSON
|
|
1190
1479
|
const r = await collectStore(env, store, now, reportDate, { enabled, need })
|
|
1191
|
-
saveSnapshot(r, store)
|
|
1480
|
+
if (!FLAG_DRY) saveSnapshot(r, store) // 回放/预览不落盘(⛔ 不覆盖生产快照)
|
|
1192
1481
|
reports.push({ store, r })
|
|
1193
1482
|
} catch (err) {
|
|
1194
1483
|
console.error(`✗ 采集 ${store.name}(${store.site})失败:${err.message}`)
|
|
@@ -1216,7 +1505,7 @@ async function main() {
|
|
|
1216
1505
|
}
|
|
1217
1506
|
return { store, r, blocks: renderStoreBlocks(store, r, topN, r.threshold, enabled, { cached: cadence.cached, onWeeklyRendered }) }
|
|
1218
1507
|
})
|
|
1219
|
-
if (cadence.active.size) saveSectionCache(sectionCache) //
|
|
1508
|
+
if (cadence.active.size && !FLAG_DRY) saveSectionCache(sectionCache) // dry-run 不写缓存
|
|
1220
1509
|
const dateLabel = okReports[0].r.date
|
|
1221
1510
|
// 按需问答(给了 --sections / --store)时,标题要能一眼看出"这是什么范围的数据",
|
|
1222
1511
|
// 而不是让人以为又是一份完整日报(2026-09-27 第 4 批)。
|
|
@@ -1249,6 +1538,12 @@ async function main() {
|
|
|
1249
1538
|
for (const g of blocks.groups) elements.push(collapsiblePanel(g.group, g.lines))
|
|
1250
1539
|
}
|
|
1251
1540
|
if (failed.length) elements.push({ tag: 'markdown', content: `⚠️ **数据缺失**:${failed.map((f) => `${f.store.name}(${f.r.error || '未知错误'})`).join(';')}` })
|
|
1541
|
+
const payload = buildReportPayload({ dateLabel, blocksList, failed })
|
|
1542
|
+
const useTable = pick(cfg, env, 'mlDailyTableCard', 'ML_DAILY_TABLE_CARD', 'true') !== 'false'
|
|
1543
|
+
if (useTable) {
|
|
1544
|
+
console.log(JSON.stringify(buildTableCard(payload)))
|
|
1545
|
+
return 0
|
|
1546
|
+
}
|
|
1252
1547
|
const card = { schema: '2.0', config: { wide_screen_mode: true },
|
|
1253
1548
|
header: { template: riskLevel(blocksList[0].blocks), title: { tag: 'plain_text', content: title } },
|
|
1254
1549
|
body: { elements } }
|
|
@@ -1264,10 +1559,18 @@ async function main() {
|
|
|
1264
1559
|
}
|
|
1265
1560
|
|
|
1266
1561
|
try {
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1562
|
+
const payload = buildReportPayload({ dateLabel, blocksList, failed })
|
|
1563
|
+
// ERP 完整日报先上传;失败只告警,不阻断群推送
|
|
1564
|
+
if (pick(cfg, env, 'mlDailyUploadErp', 'ML_DAILY_UPLOAD_ERP', 'true') !== 'false') {
|
|
1565
|
+
await publishReportPayload(payload, env)
|
|
1566
|
+
}
|
|
1567
|
+
const useTable = pick(cfg, env, 'mlDailyTableCard', 'ML_DAILY_TABLE_CARD', 'true') !== 'false'
|
|
1568
|
+
let cards = useTable
|
|
1569
|
+
? [buildTableCard(payload)]
|
|
1570
|
+
: blocksList.map(({ store, blocks }) =>
|
|
1571
|
+
cardMessageV2(`📊 美客多日报 · ${dateLabel}|${store.name}`, blocks, { template: riskLevel(blocks) }))
|
|
1269
1572
|
let overview = null
|
|
1270
|
-
const wantOverview = pick(cfg, env, 'mlOverviewCard', 'ML_OVERVIEW_CARD', 'true') !== 'false'
|
|
1573
|
+
const wantOverview = !useTable && pick(cfg, env, 'mlOverviewCard', 'ML_OVERVIEW_CARD', 'true') !== 'false'
|
|
1271
1574
|
if (wantOverview && blocksList.length > 1) {
|
|
1272
1575
|
const items = blocksList.map(({ store, r, blocks }) => {
|
|
1273
1576
|
const riskLine = (blocks.risk.find((l) => l.includes('待办')) || '').replace(/\*\*/g, '').slice(0, 60)
|
|
@@ -1277,7 +1580,7 @@ async function main() {
|
|
|
1277
1580
|
overview = cardMessageV2(`📊 美客多日报 · ${dateLabel}|跨店总览`, buildOverviewBlocks(dateLabel, items),
|
|
1278
1581
|
{ template: items.some((x) => x.level === 'red') ? 'red' : items.some((x) => x.level === 'yellow') ? 'yellow' : 'green' })
|
|
1279
1582
|
}
|
|
1280
|
-
if (failed.length) {
|
|
1583
|
+
if (failed.length && !useTable) {
|
|
1281
1584
|
cards.push(cardMessageV2(`⚠️ 美客多日报 · ${dateLabel}|部分店铺未取到`, {
|
|
1282
1585
|
head: [`**以下店铺本轮采集失败**(其余店铺正常)`, ''],
|
|
1283
1586
|
risk: failed.map((f) => `🔴 **${f.store.name}**:${f.r.error || '未知错误'}`),
|
|
@@ -137,6 +137,14 @@ const SELF_CHECKS = [
|
|
|
137
137
|
pass: "dsh 加固补丁校验通过(render 形状 + append 边界)",
|
|
138
138
|
fail: "dsh 加固补丁校验**失败** —— 补丁未生效或上游已改动,先别重启!见 AGENTS.md「会话毒化事故」",
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
// 2026-10-17 新增:这类"加载期放宽"补丁最危险的失效方式是**升级换新树后没跟着打** ——
|
|
142
|
+
// 没有任何运行期报错,只在某个旧会话打不开时才暴露(本机 06732069 实测)。
|
|
143
|
+
file: "verify-turn-outside-replace.mjs",
|
|
144
|
+
label: "v4 turn 外替换",
|
|
145
|
+
pass: "v4 加载放宽补丁校验通过(turn 外替换型 tool/result 不再判损坏)",
|
|
146
|
+
fail: "v4 加载放宽补丁**缺失** —— 旧会话会重新打不开(`tool/result is outside an open turn`),先别重启!",
|
|
147
|
+
},
|
|
140
148
|
];
|
|
141
149
|
|
|
142
150
|
// --patches-only:只重打补丁 + 加固校验 + 校验旧 session 迁移 + preset 门禁,**不动全局 scope**(升级后/排查时可单独跑)
|
|
@@ -145,10 +153,11 @@ if (patchesOnly) {
|
|
|
145
153
|
const r = applyHotfixPatches();
|
|
146
154
|
const g = runPresetGates();
|
|
147
155
|
// 自检项由 applyHotfixPatches 内的 SELF_CHECKS 声明(新增一个 verify-*.mjs 只需在那里加一行)
|
|
148
|
-
|
|
156
|
+
// 迁移校验**不进 bad**(2026-10-17 对齐 AGENTS.md:已退役 A 类 ⇒ 只 warn 不判红)
|
|
157
|
+
const bad = r.selfChecks.some((c) => c.state === false) || g.schema === false || g.boot === false;
|
|
149
158
|
const selfText = r.selfChecks.map((c) => `${c.label}=${c.state === undefined ? "跳过" : c.state ? "通过" : "失败"}`).join("、");
|
|
150
159
|
console.log(`\n[patches-only] 完成:补丁 ok=${r.ok} warn=${r.warn}、${selfText}`
|
|
151
|
-
+ `、迁移校验=${r.migration === null ? "跳过" : r.migration ? "通过" : "
|
|
160
|
+
+ `、迁移校验=${r.migration === null ? "跳过" : r.migration ? "通过" : "预期红(v0/v3 老日志已退役,不判红)"}`
|
|
152
161
|
+ `、preset schema=${g.schema === null ? "跳过" : g.schema ? "通过" : "失败"}、启动冒烟=${g.boot === null ? "跳过" : g.boot ? "通过" : "失败"}`);
|
|
153
162
|
if (bad) console.log(" ⛔ 有门禁未通过 —— 先别重启服务!");
|
|
154
163
|
process.exit(bad ? 1 : 0);
|
|
@@ -452,10 +461,11 @@ try {
|
|
|
452
461
|
console.log("\n[7/7] 完成。");
|
|
453
462
|
console.log(" ✅ 升级成功(未重启服务,旧进程仍用旧代码)");
|
|
454
463
|
const failedSelfChecks = hotfix.selfChecks.filter((c) => c.state === false);
|
|
455
|
-
|
|
464
|
+
// 迁移校验**不进阻断判据**(2026-10-17 对齐 AGENTS.md「已退役 A 类」口径:只 warn)
|
|
465
|
+
if (hotfix.migration === false) console.log(" ⚠️ 旧 session 迁移校验未通过 —— **按口径属预期**(v0/v3 老日志已退役;需要时按手册临时恢复补丁)");
|
|
466
|
+
if (failedSelfChecks.length > 0 || gates.schema === false || gates.boot === false) {
|
|
456
467
|
console.log(" ⛔ 门禁未全部通过 —— 先别重启服务!修好下面这些再重启:");
|
|
457
468
|
for (const c of failedSelfChecks) console.log(` · 补丁自检失败:${c.label}(node scripts/patches/${c.file} 看详情)`);
|
|
458
|
-
if (hotfix.migration === false) console.log(" · 旧 session 迁移校验失败(见 AGENTS.md「session 迁移 hotfix」)");
|
|
459
469
|
if (gates.schema === false) console.log(" · preset schema 门禁失败(node scripts/validate-preset-schema.mjs --runtime)");
|
|
460
470
|
if (gates.boot === false) console.log(" · 启动冒烟失败(node scripts/plugin-bootcheck.mjs,含 preset 挂载)");
|
|
461
471
|
console.log(" 或回滚(见下方回滚步骤)后再重启");
|
|
@@ -566,7 +576,10 @@ function applyHotfixPatches() {
|
|
|
566
576
|
ok(`旧 session 迁移校验通过(${samples.length} 个样本:v0 与 v3/v4 各最新/最旧)`);
|
|
567
577
|
out.migration = true;
|
|
568
578
|
} catch {
|
|
569
|
-
|
|
579
|
+
// 2026-10-17:与 AGENTS.md「已退役 A 类迁移补丁」口径对齐 —— 该项**只 warn、不判红**
|
|
580
|
+
// (此前文案已改成"预期",判据却仍算失败 ⇒ 每次 `--patches-only` 都以"先别重启服务"收尾,
|
|
581
|
+
// 把人训成忽略门禁;本机实测红项来自 v0 会话的 permission/preset `origin`,正是退役补丁覆盖的那条)。
|
|
582
|
+
warn("旧 session 迁移校验未通过 —— **按口径属预期**(已退役 A 类迁移补丁 ⇒ v0/v3 老日志不再要求可加载;\n 工作连续性由 ~/.dsh/argent/session-handoffs/INDEX.md 的续接摘要承担;确需打开老会话见\n docs/内部运维-dsh升级与回滚手册.md「0.1.5/0.1.7 session 迁移 hotfix」临时恢复补丁)");
|
|
570
583
|
out.migration = false;
|
|
571
584
|
}
|
|
572
585
|
}
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
"minPlan": "enterprise",
|
|
150
150
|
"pack": {
|
|
151
|
-
"version": "1.2.
|
|
151
|
+
"version": "1.2.176",
|
|
152
152
|
"scripts": [
|
|
153
153
|
"scripts/wenyao-invoice-writeback.mjs",
|
|
154
154
|
"scripts/invoice_lip_runner.py",
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
],
|
|
252
252
|
"minPlan": "enterprise",
|
|
253
253
|
"pack": {
|
|
254
|
-
"version": "1.3.
|
|
254
|
+
"version": "1.3.181",
|
|
255
255
|
"scripts": [
|
|
256
256
|
"scripts/poll-fulfillment.sh",
|
|
257
257
|
"scripts/orchestrator.py",
|
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
"minPlan": "pro",
|
|
332
332
|
"_minPlanNote": "**2026-10-09(变现分层落地)**:`enterprise` → **`pro`**。判据 = 用户已确认的产品模型「分界线 = **价值更高 / 风险更高 ⇒ 企业版**」:日报是**只读**能力(官方 API 只读查询 + 生成推送,⛔ 不写客户任何系统、⛔ 不动浏览器),与 `full`(箱唛/写操作)、`invoice`(发票/门户写操作)不同档 ⇒ 归 **Pro**。⛔ 只改**档位下限**,⛔ 不改流程行为、⛔ 不改价格口径;`daily-report` 的能力包版本随引擎前进(见 pack.version)。已装机器**不因本改动被卸载/停用**(缩权只影响后续下发,见 entitlements.plans._why 与 docs 策划案 §4.4)。",
|
|
333
333
|
"pack": {
|
|
334
|
-
"version": "1.1.
|
|
334
|
+
"version": "1.1.174",
|
|
335
335
|
"scripts": [
|
|
336
336
|
"scripts/daily-report.mjs",
|
|
337
337
|
"scripts/daily-report.sh",
|
|
@@ -433,7 +433,7 @@
|
|
|
433
433
|
}
|
|
434
434
|
]
|
|
435
435
|
},
|
|
436
|
-
"version": "0.1.
|
|
436
|
+
"version": "0.1.82",
|
|
437
437
|
"_note": [
|
|
438
438
|
"能力包版本(语义化):改了该能力的流程/提示词/脚本/技能 ⇒ 必须提版本并重新打包 |**0.1.40(2026-10-04,角色退役·墓碑)**:包内容变更(`presets/argent-hr-office/agent.cordis.yml` 的 persona 改成退役告知、`preset.yml` 的 name/description 标注「已退役」)—— 角色 `hr-office` 已于 2026-10-04 退役(方案 §3.3.1:gateway.json 条目与 install-dsh.sh 安装期入口已撤),**本能力包保留**(装机内容不变、仍可安装;入口 preset 作为 web 老会话的墓碑保留)⇒ 版本随内容前进。|**0.1.41(2026-10-09,版本随内容前进)**:`0.1.40` **发布之后其字节被原地覆盖过一次**(服务端清单 `sha256` `15cf4f43…` → `058ccead…` 而 `version` 原地不动,见 whyshu-svr `docs/ops/deploy-fe02231-packs-align.md`)⇒ 同一个 URL 下的内容变过 ⇒ \"内容变 ⇒ 版本变\"这条不变式被破坏,客户机无法靠版本号判断自己拿到的是哪一份。本次**只提版本、包内容一字未改**(重盖章后内容摘要与 0.1.40 时逐字相同)⇒ `hr-0.1.41.tgz` 是该内容**首次以某个版本号**对外发布。⛔ 未改共用引擎、未新增运行期文件,故 `pack.engine` 清单不变。|**0.1.42(2026-10-09,配置地基:配置目录 + 通用读取器 —— hr 包内容随之改变)**:本能力**无 flow**,按 `pack.engine` 的分发规矩**不背共用引擎**(`files` 仍只有 `presets/argent-hr-office/**` 三件,`integrity` 逐字节未变)。但**每个包的 tgz 里都带一份注册表投影**(`scripts/flow-requirements.json` 的引擎级段),而本批改了它的 `pack.engine`(新增 `core/lib/config-catalog.mjs` 与 5 个 `configs/*.json`)⇒ **hr 的包字节变了** ⇒ 按「内容变 ⇒ 版本必变(含无 flow 的包)」这条不变式**必须一起提版本**,否则会出现\"同一个版本号 0.1.41 对应两份不同字节\"(这正是 `release-argent.sh --check-dl` 的能力包档当场抓到的形态:`✗ hr: 版本相同(0.1.41)但内容摘要不同 —— 仓库 ad3599c5 vs 线上 09e1fef9`;提版后该条转为 ⚠「发布在途」,不再判红)。⛔ 本轮 hr 的**能力内容一字未改**。|**0.1.44(2026-10-09,预约失败判定链 + 口径统一)**:共用引擎 `scripts/flow_failure.py` 变更(新增 `canonical_reason()` 与 `--case` —— 打印某 case 的**规范失败原因**:优先读台账 `failed.reason`,老台账回落到 `error` 去身份前缀;给角色/运维一个**逐字照抄**的取数口,杜绝\"表备注写 A、账本写 B\")⇒ 所有能力包的注册表投影同批变化。本能力自身:⛔ 本能力内容一字未改;但 `flow_failure.py` 在 `pack.engine` 内,且包内 `scripts/flow-requirements.json` 投影**含 `failureClasses`**(本次新增的两条 needsHuman 判据就在其中)⇒ **包字节变了**,按「内容变 ⇒ 版本必变(含无 flow 的包)」这条不变式必须一起提版本。(⚠️ **盖章说明**:本版内容在同一工作树里经过**多轮收口**(先落实现,再按 vertical-lint 的引擎业务词约束改注册表文案、再收口文案)⇒ 以**最终内容**盖章;版本号从未发布过,不存在\"内容变了版本没动\"的对外窗口。)|**0.1.45(2026-10-09,三处生产实测收口:票不许静默消失 · 失败必写原因+动作 · 发票流程 Rule R)**:共用引擎变更 ⇒ 四个能力包一起提版本并重盖章。① `scripts/flow_triage.py`:判定改为**有效末条事件**(`INFO_EVENTS = phase/wake/policy_apply/policy_new` 回退到最近一条状态性事件;`failed_ack` = 有意不动且**不参与回退**)、`blocked` 从「静默 pass」改为可见的 `needs_human`,并加**循环后对账兜底**(任何未归桶的 case ⇒ `needs_human`,kind=unclassified)——修的是客户机生产实测 P0(某发票票):其尾部是 `blocked → phase` ⇒ 分诊摘要全 0、运营勾了放行也不派发、且**不在「需人工」里**(静默卡死)。② `scripts/flow_failure.py`:新增运营文案**唯一来源** `ops_message()`(`结论:<业务类|系统类|我方缺陷类>|<原因>|动作:…`,≤300 字)与 `release_hint()`(放行位/提示的**唯一实现**:变体与**顶层** pick 都认;`orchestrator._ops_action_hint` 改为委托它);③ **新增** `scripts/invoice_fail_closeout.py`(属**发票能力专属脚本** `capabilities.invoice.pack.scripts`,⛔ 不进共用引擎 —— 它只写发票那一列):失败/成功收口 —— 把**同一串**写进表「发票错误信息」与台账(`detail` 逐字相同、`error` 放规范原因),取不到原因 ⇒ **exit 2 拒绝执行**,代码级异常归「我方缺陷类」且**不回原文**,成功 ⇒ `--clear-error` 清空;④ `scripts/flow_return_to_ops.py`:缺口回填文案改用同一来源(表字段 == 台账 `detail`);④b `scripts/flow_config.py`:归一化的 `guards` 增**标量透传**(`writer`=写手路径、`errorField`=失败原因列 —— 不透出就只能各自把路径/列名写死,那正是「判据第二份」与业务词泄漏进引擎的来源);⑤ 业务线声明(`verticals/<id>/flows.json`):两个发票流程新增 `pick.release` / `pick.releaseReDispatch`(放行位 = **各自的门控列**「发票就绪」;上限 3 / 冷却 30)与 `guards.errorField`。本能力自身:⛔ 内容一字未改(无 flow ⇒ 不背共用引擎);但包内注册表投影含 `failureClasses`/`pack.engine` ⇒ 包字节变了,按「内容变 ⇒ 版本必变(含无 flow 的包)」一起提版本。|**0.1.46(2026-10-09,配置「生效来源」可见化 + 冲突告警,批 1)**:本能力无 flow ⇒ **不背共用引擎**(`files` 仍只有 3 件 preset,逐字节未变);但包内注册表投影含 `pack.engine` ⇒ 包字节变了,按「内容变 ⇒ 版本必变(含无 flow 的包)」一起提版本(同 `0.1.42` 先例)。本能力内容一字未改。共用引擎 `core/lib/config-catalog.mjs` 新增**纯函数** `resolveEffective()`(按规范链算「谁赢了 / 谁被压住」)、密钥脱敏 `redactSecret()` 与读取器 `effectiveOf()/levelsOf()`,并新增只读 CLI `scripts/config-explain.mjs`(`--key/--all/--list/--json`)⇒ 能力包内容变化,按纪律提版本并重盖章。⛔ **零行为变化**:读取器的取值路径(`resolveOne`)与任何 `todayChain` 一个字节都没改,只是「如实展示」;同批回归 `scripts/tests/config-effective.test.mjs`(63 断言)+ 端点 / 页面两条既有回归扩测。|**0.1.47(2026-10-09,同批收口:脱敏闸幂等)**:只读 CLI `scripts/config-explain.mjs` 的\"再脱敏\"兜底闸原先**无条件**执行 ⇒ `❰已设置(长度N)❱` 里的 N 变成**占位串自己的长度**(恒 11,实测),而用户正是拿它比对\"密钥换没换\" ⇒ 已改为**幂等**(含被压住的那级)并补回归;引擎面同一份 CLI 变了 ⇒ 包内容变化,按纪律再提版本并重盖章。本能力自身内容一字未改。|**0.1.49(2026-10-10,A 批:按智能体安装清单 —— 客户从「智能体库」选一个装,只装它的东西)**:① **`pack.engine` 新增两项**:`agents/catalog.json`(L0 货架声明:agent → capability + flows 子集 + 角色 + 插件/技能/集成/凭据槽)与 `scripts/argent-agents.mjs`(声明解析/校验/清单构造;安装器与门禁**共用一份**判据)⇒ 引擎清单与每包内的注册表投影都变了(**无 flow 的 hr 也变**,按「内容变 ⇒ 版本必变(含无 flow 的包)」这条不变式一起提版本);② **共用引擎行为变更**(`install-argent.mjs` / `scripts/lib/preset-install.mjs` / `scripts/argent-upgrade.mjs`):新增 `--agent <id>[,<id>…]` / `--list-agents` / `--internal-roles` / `--capability remove <id>`(写前备份 + 审计);preset **行落盘集合**改为「按清单收窄」(`--agent` ⇒ 恒装 + 本智能体角色 + 已装集;默认 ⇒ 客户可见集 + 已装集;**内部角色默认不落**);升级器按 `<dshHome>/argent/agents.json` **重新施加**同一集合(否则一次升级就把收窄还原);**未给 `--agent` 时 CLI 契约与计划清单输出与改动前逐字一致**(差分回归钉住);③ 本能力自身:**新增活的 HR 角色** `presets/argent-hr/`(招聘支持 / 员工关系 / PPT 与文档产出 / 员工隐私严格保密)+ `pack.presets` 收它 ⇒ 本能力从「只有墓碑」变成「有入口」;设置页入口字段由 `hrOfficeRole`(hr-office) 改为 `hrRole`(argent-hr),旧角色改用 `hrOfficeTombstone` 字段如实报告(⛔ 墓碑不复活、⛔ 不当入口,仍随包落位供老会话 agentPreset 外键解析)。回归:`scripts/tests/agent-catalog.test.mjs`(110 断言,七条判据:只落本智能体 / 子集过滤 / 向后兼容差分 / 未知输入零写入 / remove 备份+审计 / 目录 schema / 内部角色默认不落)。|**0.1.50(2026-10-10,同批二次收口:宿主资产声明 + 门禁)**:同一工作树里第二次收口(先落 `--agent` 实现并盖章,再补 `capabilities.<c>.pack.hostAssets`(**宿主侧资产声明位**:定时器/登录自启/桌面项)与它的门禁 C12,并把 `scripts/argent-agents.mjs` 的清单/回执接上宿主资产)⇒ 引擎面文件内容变了,按「内容变 ⇒ 版本必变」纪律**再提一版**(先例同 1.3.104→1.3.105、`docs/Argent-智能体平台-设计.md` §8.3 第 3 条)。版本号 0.1.49 从未发布过,不存在「内容变了版本没动」的对外窗口。⛔ 本能力的流程/提示词/脚本内容未再改动。|**0.1.51(2026-10-10,同批三次收口:安装器用法文案 · 门禁登记 · 引擎面去业务词)**:同一工作树里第三次收口 —— ① `install-argent.mjs` 的**用法头注释**补上 `--agent` / `--list-agents` / `--internal-roles` / `--capability remove` 四个入口(内容变 ⇒ 引擎 sha 变);② `scripts/tests/is-direct-run.test.mjs` 的**唯一登记处**加入 `scripts/check-agent-catalog.mjs`(新增 CLI 必须登记,否则端到端判据覆盖不到它;测试文件不在包内,本条只为留痕);③ 本文件里本批新加的 `_note` 文案去掉业务词(`vertical-lint` 的 engine-words 档按**基线**只拦新增,⛔ 不抬基线 ⇒ 改写文案而不是改基线)⇒ 引擎面内容再变,按纪律**再提一版**。版本号 0.1.50 从未发布过,不存在「内容变了版本没动」的对外窗口。⛔ 本能力的流程/提示词/脚本内容未再改动。|**0.1.52(2026-10-10,仓容前置探测批 —— 注册表投影变化,hr 必须跟随提版)**:本批改了共用引擎面 `scripts/flow-requirements.json`(新增 `failureClasses.capacityFull`)、`scripts/flow_capacity*`/`ml_capacity_probe.py`、`flow_failure.py` 分类顺序、`configs/advanced.json`(`advanced.appointment.max_day` 默认 5)与编排器接入点 ⇒ **每个包的 tgz 里都带一份注册表投影**(含 `failureClasses`/`pack.engine`)⇒ hr 的包字节随之改变。本能力自身内容一字未改(`files` 仍只有两个 preset 目录)。⚠️ **教训**:本批曾按 `--describe hr`(只列 6 个 preset 文件)判定\"hr 不含 pack.engine ⇒ 内容没变\",漏掉了\"注册表投影在包内\"这一层 ⇒ 被 `release-argent.sh --check-dl` 当场抓成 `✗ hr: 版本相同(0.1.51)但内容摘要不同 ⇒ 同版本覆盖发布的形态`;按不变式提版后该条转为 ⚠「发布在途」。先例:0.1.42 / 0.1.44 / 0.1.46(均为\"改了引擎基表 ⇒ 无 flow 的包也提版本\")。|**0.1.55(2026-10-11,同批:引擎面新增 `scripts/lib/cdp_ports.py`)**:本能力**无 flow ⇒ 不背共用引擎**(`files` 仍只有两个 preset 目录,逐字节未变);但包内注册表投影含 `pack.engine` ⇒ 包字节变了,按「内容变 ⇒ 版本必变(含无 flow 的包)」提版本并重盖章(同 `0.1.42` / `0.1.52` 先例)。本能力内容一字未改。|**0.1.56(2026-10-12,Windows 安装路径三缺陷修复批)**:共用引擎变更(引擎面文件 ⇒ 每个包的注册表投影都变)——① `scripts/lib/preset-install.mjs`:**「已装集」判据改为选择痕迹** `presets.selection.json#kept`(⛔ 不再读 `package.json#dsh.bundle.patch` —— pnpm 对 `file:` 依赖建的是指向货架的链,读到的永远是货架自带的 14 条 ⇒ 收窄被自己的安装步骤抹掉),新增 `installedSelectionIds()` / `materializeBundleDir()`(软链**实体化**后再改写,⛔ 不走链写进仓库)/ `checkPresetSelectionConsistency()`(patch 实体 == presets.json 行 == dsh.bundle.patch == 痕迹);② `install-argent.mjs`:新增**第 7c2 步收尾重施**(第 7 步 `pnpm add file:…` 会把 profile 副本换回货架 14 个 ⇒ 旧实现在 winpc 真机\"声称只落 7/14、装完却是 14\",7 个内部角色永久留下)、版本体检改**两个候选落点**(克隆目录 + 安装器自己那棵树 —— Windows 的 boot 克隆场景,此前永远退回内置列表 ⇒ 现役 `0.1.7-rc.2` 被报\"未验证\")、`profile-doctor` 与升级器子进程**显式传 `DSH_HOME`**(否则 `--dsh-home <临时>` 时改的是真实 `~/.dsh`)、完成提示**按平台**给启动/重启指引(Windows 不再打 bash 的 `DSH_HOME=… dsh web …`);③ `scripts/lib/dsh-versions.mjs`:内置基线补 `0.1.7-rc.2` + 新增 `verifiedListGaps()` **防漂移门禁**;④ `scripts/argent-upgrade.mjs`:同一判据 + 收尾重施。⇒ 按「内容变 ⇒ 版本必变(含无 flow 的包)」四个包一起提版本并重盖章。本能力内容**一字未改**(无 flow ⇒ 不背共用引擎,仍因包内注册表投影而变)。回归:`scripts/tests/preset-landing-selection.test.mjs`(48 断言:全新机器恰 7 个客户可见角色 / 客户机的 7 再跑安装**仍 7**(垫片 pnpm 中途真把它放大过)/ 14 坏态收窄到 7 / 三处集合+痕迹不变量 / 软链不走链改写仓库 / 真 pnpm 的机制证据)、`scripts/tests/dsh-verified-list.test.mjs`(21 断言:lock 里已晋级版本必须全在列表内(构造缺项 ⇒ 红)、`candidate/` 不参与、列表唯一真源、boot 克隆场景真跑一次)。⛔ 本批**未发布**(发布留给交付方)。|**0.1.57(2026-10-12,升级路径角色落盘追修批:已装集唯一来源 + 内部角色不变式)**:本能力**无 flow ⇒ 不背共用引擎**(`files` 仍只有 preset 目录,逐字节未变);但包内注册表投影含 `pack.engine` ⇒ 包字节变了,按「内容变 ⇒ 版本必变(含无 flow 的包)」一起提版本并重盖章(同 `0.1.42` / `0.1.52` 先例)。本能力内容**一字未改**。① `scripts/lib/preset-install.mjs`:「已装集」的**唯一来源**收口到 `resolveInstalledSelection()`(**可信痕迹** `presets.selection.json#kept` → 退回 `agents.json` 里各 agent 的 `roles` 并集 → 两级都没有 ⇒ **空集**=首次安装语义;⛔ **绝不**把 profile 里的 patch 实体 / `presets.json` 行 / `dsh.bundle.patch` 条目当已装集 —— 那三份铺完恒是 14,「客户可见 ∪ 已装 ∪ 本智能体」于是恒等于 14,收窄被自己的安装步骤抹掉),并给「可信」加了**判据**:痕迹含 `visibility: internal` 的角色而本机 `internalRoles ≠ true`(且未给 `--internal-roles`)⇒ 判不可信、**响亮报告**后按「痕迹缺失」处理(现场那份 `kept:14/excluded:[]` 的痕迹正是放大缺陷写出来的);新增**不变式** `checkNoInternalLanding()` —— 落盘集合不得含内部角色(除非 `--internal-roles` 或 `agents.json#internalRoles === true`),违反 ⇒ `applyPresetSelection()` **判红并拒绝写出**(⛔ 不静默剔除、不静默修);新增 `reseedBundleDir()` 与 `applyPresetSelection({reseedFromShelf})`(收尾重施必须**按货架重播再收窄**:副本已被削到 7 时就地再过滤永远只能得 7,`--internal-roles` 装不回来、自检还会拿「期望 14 ≠ 落盘 7」判**假红**);② `scripts/argent-upgrade.mjs`:**升级档与安装档同一条公式**(客户可见集 ∪ 可信已装集 ∪ 本智能体角色),⛔ 不再用 `agentScoped: agents.length>0`(它把客户可见集整个排除掉 ⇒ 现场那台只落 2 个角色);第 7.6 步收尾重施带 `allowInternal` + `reseedFromShelf`,判红即该步 `ok:false`;③ `install-argent.mjs`:第 4 步默认档 / `--agent` 子集 / 第 7c2 步收尾重施 / `--capability remove` 四处统一走可信已装集与授权口径(`--capability remove` 只为「本机已合法装了内部角色」时不误判红);④ **新增只读 CLI** `scripts/check-preset-landing.mjs`(四件套 + 已装集来源 + 两条不变式,任何一台机器可照抄自查,⛔ 不写任何文件)⇒ `pack.engine` 登记项 +1。**现场(winpc 真机,2026-10-12)**:profile 14 个 patch 实体 + 痕迹 `kept:14/excluded:[]` + `agents.json=[hr]` + `internalRoles=false`,重跑官网一条命令(升级路径)修前打的是 `preset 同步(行式): **14** 个角色` + `角色落盘集合收尾:14 个;三处集合 + 痕迹一致`(7 个内部角色一个没摘),修后同一台机器 ⇒ **7**(`excluded` 列出 7 个内部角色)并打印 `选择痕迹不可信 ⇒ 已装集退回 agents.json 角色`。回归:`scripts/tests/preset-landing-upgrade-path.test.mjs`(43 断言:现场复刻升级路径 ⇒ 7 且 **pre-fix 判据重建得 14**(鉴别力)/ `agents.json` 缺失 ⇒ 7 / 可信痕迹 7 不许放大 / `internalRoles:true` ⇒ 14 放行 / **不变式负样本判红且零写入** / 「铺货架 ≠ 选择」/ 现场真形态 `kept:14` ⇒ 7 且响亮报告 / 真 home 零增量)。⛔ 本批**未发布**(发布留给交付方)。|**0.1.59(2026-10-12,P1-A 宿主排程批:四道闸防线 + 日报试点声明)**:本能力的流程/提示词/脚本**一字未改**;共用引擎面变更(`scripts/flow_config.py` 新增 trigger.schedule 解析、`scripts/vertical-lint.mjs` 新增排程声明门禁、`scripts/lib/registry.mjs` 新增只读 flowSchedule())与**包内注册表投影**变化(业务线声明只动了日报;新增的 L1 `scripts/lib/schedule-probe.mjs` 与 L2 `scripts/argent-schedule.mjs` **刻意不进 pack.engine**,理由见 daily-report 的 `_note`)⇒ 按「内容变 ⇒ 版本必变(含无 flow 的包)」一起提版本并重盖章。新增回归 `scripts/tests/schedule-probe.test.mjs` / `scripts/tests/schedule-declaration.test.mjs`。|**0.1.62(2026-10-12,宿主门禁追修批:⛔ 不代装宿主 + npx 形态纳入宿主解析)**:共用引擎变更(引擎面文件 ⇒ 每个包的注册表投影都变,**含无 flow 的 hr**)——① `install-argent.mjs`:**删除** `--install-host` 代装入口(连同旧自动代装函数与宿主离线包的下载/解包/bin 链接逻辑;开关若仍被传入 ⇒ 报「该开关已废弃」+ **非零退出**(1 = 用法错误),⛔ 不静默忽略),被拒文案改为按 dsh **官方**运行方式给指引(首选 `npx @deepseek-ai/dsh@<窗口内版本> web`;备选官方「从源码运行」;四要素:① 你的版本不在支持窗口(列窗口)② 官方方式换成窗口内版本的一行命令 ③ 我们正在适配你的版本(⛔ 不给对外 ETA)④ 未改动你的 dsh);② **新增** `scripts/lib/dsh-host-resolve.mjs`(**进 `pack.engine`**):宿主解析链 = **PATH 上的 `dsh` → `DSH_BIN` → npx 缓存形态**(读 `<npm cache>/_npx/<hash>/node_modules/@deepseek-ai/dsh/package.json` 的 version,纯只读零网络;多候选按「最近使用优先(mtime 最新)+ 同刻路径升序」确定并**打印选了哪个**)⇒ **npx 形态算宿主**(旧口径「npx 不等于已安装宿主」作废),且 npx 形态下插件装配走**同一条 launcher**(`npx --yes @deepseek-ai/dsh@<版本> plugin --profile web add file:…`),⛔ 不许「检测到 npx 却去用 PATH 上的另一个 dsh」;③ `install.sh` / `install.ps1` 口径同步(ps1 仍声明并透传 `-InstallHost`,只为让旧命令行得到**可读**的「已废弃」原因而不是参数绑定错误)。本能力自身流程 / 提示词 / 脚本**一字未改**。回归 `scripts/tests/install-host-gate.test.mjs`(**219 断言**,纯夹具:垫片 dsh/npx + 本地裸库 + 临时 home,⛔ 零外网、⛔ 不碰真 `~/.dsh`;⚠️ `dsh plugin` 会绕过 `DSH_HOME`/`HOME` ⇒ 夹具只断言命令行、绝不真跑)。⛔ 本批**未发布**(发布留给交付方)。",
|
|
439
439
|
"2026-10-12:仓容前置探测**误报**修复(判据收紧为「严格阻断形态」+ 手动/CLI 跑完落终态台账);注册表投影在每个包内 ⇒ 四包一起提版重盖。",
|
|
@@ -644,9 +644,7 @@
|
|
|
644
644
|
"scripts/invoice_ready_audit.py",
|
|
645
645
|
"scripts/invoice_carrier_notice.py",
|
|
646
646
|
"scripts/ziniao-auth-probe.sh",
|
|
647
|
-
"install-argent.mjs",
|
|
648
647
|
"scripts/install-license.mjs",
|
|
649
|
-
"scripts/install-one-shot.mjs",
|
|
650
648
|
"scripts/migrate-providers.mjs",
|
|
651
649
|
"agents/catalog.json",
|
|
652
650
|
"scripts/argent-capabilities.mjs",
|
|
@@ -748,4 +746,4 @@
|
|
|
748
746
|
"_note": "**「定时怎么来」的唯一声明点**(0.6.0 W8,2026-10-14)。取值 `auto | native | activation-shim | none`:`auto` = 按宿主证据决定(探到 `@deepseek-ai/dsh-schedule` ⇒ native,否则按垫片证据)。⭐ 判据**不在这里**:唯一实现是 `scripts/lib/schedule-mechanism.mjs#resolveScheduleMechanism()`;本段只是**声明**(谁在什么条件下被允许),运行期只有两个读取点 —— Node 侧读本段经 `scripts/lib/registry.mjs`(合并唯一真源),Python 侧 `scripts/flow_config.py --schedule-declaration`。`jobs` = **激活垫片**(0.1.7 线)的安装入口清单:`labelPrefix`(LaunchAgent label 前缀)、`installCommand`(模板,占位 `{repo}`/`{flow}`)、`source`(垫片实现文件)。⛔ 两条线**不许同时**调度同一条流程(单一调度者纪律):native 与 shim 同时在 ⇒ 响亮报冲突(`schedule-mechanism.mjs#schedulerConflict()`,`onConflict: refuse` = ⛔ 不静默择一)。⛔ 探测不到时一律 `unknown`(**不假装可用、不静默装 cron**)——F1(`resolveDshScope()` 可能返回不存在的路径且不感知 npx)未修,所以 native 探测必须分三态:available / absent / unknown。"
|
|
749
747
|
},
|
|
750
748
|
"_engineNoteReleaseBudget20261013": "2026-10-13(**放行额度随上游数据变化重置 + 外部时序独立通道**,修「上限用尽后再也不会被拾取」)。① 引擎面内容变更:`scripts/flow_triage.py`(新增上游逐字段稳定哈希判据 —— 变了才把该票的放行计数归零,并写一条**信息性** `policy_apply{policy=release_budget_reset}`;新增**独立通道**分类与长冷却/独立上限,⛔ 不占既有的每票放行上限)、`scripts/flow_claim.py`(`planned` 增记逐字段哈希快照与独立通道标记,⛔ 只加字段)、`scripts/flow_ledger.py`(`policy_apply` 增一个机器可判的政策名,⛔ 不改事件类型)、`scripts/flow_sources/common.py` 与 `scripts/flow_sources/feishu_base.py`(新增**观察列**进投影的判据 —— 不在投影里的列永远观察不到变化)、`core/contracts/argent.contracts.json`(三处 anchor 行号随上述文件重排)。② 每个包的 tgz 里都带一份注册表投影(含 `pack.engine`)⇒ 引擎面一变、**四个包一起提版本并重盖章**(含**无 flow** 的 `hr`;同 0.1.42 / 0.1.52 先例)。⚠️ 本批在同一工作树里经过**多轮收口**(先落实现,再按 vertical-lint 的引擎业务词约束去掉引擎面文案里的业务词,再收口技能/运维文案与契约 anchor)⇒ 以**最终内容**盖章;版本号**未发布**,不存在「内容变而版本没动」的对外窗口。"
|
|
751
|
-
}
|
|
749
|
+
}
|
|
@@ -41,7 +41,7 @@ pick_py() {
|
|
|
41
41
|
[ -x "$c" ] || continue
|
|
42
42
|
"$c" -c 'import openpyxl, yaml' 2>/dev/null || continue # 硬要求:配置/发票生成必需
|
|
43
43
|
"$c" -c 'import PIL, playwright' 2>/dev/null || \
|
|
44
|
-
echo "[$(date '+%F %T')] ⚠️ 解释器 $c 缺 pillow/playwright ⇒ 浏览器流程会失败(修:
|
|
44
|
+
echo "[$(date '+%F %T')] ⚠️ 解释器 $c 缺 pillow/playwright ⇒ 浏览器流程会失败(修:bash scripts/python-runtime.sh ensure)" >&2
|
|
45
45
|
printf '%s' "$c"; return 0
|
|
46
46
|
done
|
|
47
47
|
return 1
|