upstream-radar 0.43.3 → 0.43.5
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-zh-CN.md +64 -70
- package/README.md +83 -77
- package/dist/src/cli.js +22 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dsh-directory-feed.d.ts +8 -3
- package/dist/src/dsh-directory-feed.d.ts.map +1 -1
- package/dist/src/dsh-directory-feed.js +80 -10
- package/dist/src/dsh-directory-feed.js.map +1 -1
- package/dist/src/dsh-headless-agent-plan.d.ts +7 -0
- package/dist/src/dsh-headless-agent-plan.d.ts.map +1 -1
- package/dist/src/dsh-headless-agent-plan.js +20 -1
- package/dist/src/dsh-headless-agent-plan.js.map +1 -1
- package/dist/src/dsh-install-plan.d.ts +7 -0
- package/dist/src/dsh-install-plan.d.ts.map +1 -1
- package/dist/src/dsh-install-plan.js +8 -2
- package/dist/src/dsh-install-plan.js.map +1 -1
- package/dist/src/upstream-observer.d.ts +2 -0
- package/dist/src/upstream-observer.d.ts.map +1 -1
- package/dist/src/upstream-observer.js +166 -123
- package/dist/src/upstream-observer.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.zh-CN.md +19 -19
- package/package.json +2 -2
|
@@ -1490,55 +1490,96 @@ export async function runOpenAiCompatibleAgent(task, prompt, options) {
|
|
|
1490
1490
|
};
|
|
1491
1491
|
}
|
|
1492
1492
|
const endpoints = llmCompletionEndpoints(baseUrl);
|
|
1493
|
-
|
|
1493
|
+
const systemPrompt = [
|
|
1494
|
+
'只返回一个严格 JSON 对象。不要输出 Markdown、解释、前缀或后缀;输出必须以 { 开始并以 } 结束。',
|
|
1495
|
+
'Every response must contain exactly the eight fields shown below and no others.',
|
|
1496
|
+
'EXAMPLE JSON OUTPUT:',
|
|
1497
|
+
JSON.stringify({
|
|
1498
|
+
impact: 'unknown',
|
|
1499
|
+
confidence: 'low',
|
|
1500
|
+
evidence: ['State an exact repository path or explicitly name the missing evidence.'],
|
|
1501
|
+
breaking_change: 'unknown',
|
|
1502
|
+
dependency_risk: 'unknown',
|
|
1503
|
+
recommended_action: 'Review the changed runtime paths before upgrading.',
|
|
1504
|
+
urgency: 'monitor',
|
|
1505
|
+
reasoning_summary: 'Separate observed facts from model judgment.',
|
|
1506
|
+
}, null, 2),
|
|
1507
|
+
].join('\n');
|
|
1508
|
+
endpointLoop: for (let index = 0; index < endpoints.length; index += 1) {
|
|
1494
1509
|
const endpoint = endpoints[index];
|
|
1495
|
-
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1510
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1511
|
+
const repairAttempt = attempt === 1;
|
|
1512
|
+
try {
|
|
1513
|
+
const response = await fetch(endpoint, {
|
|
1514
|
+
method: 'POST',
|
|
1515
|
+
headers: {
|
|
1516
|
+
authorization: `Bearer ${apiKey}`,
|
|
1517
|
+
'content-type': 'application/json',
|
|
1518
|
+
},
|
|
1519
|
+
body: JSON.stringify({
|
|
1520
|
+
model,
|
|
1521
|
+
messages: [
|
|
1522
|
+
{
|
|
1523
|
+
role: 'system',
|
|
1524
|
+
content: repairAttempt
|
|
1525
|
+
? `The previous JSON-mode response was empty or invalid. Return the example shape now.\n\n${systemPrompt}`
|
|
1526
|
+
: systemPrompt,
|
|
1527
|
+
},
|
|
1528
|
+
{ role: 'user', content: prompt },
|
|
1529
|
+
],
|
|
1530
|
+
temperature: 0,
|
|
1531
|
+
// DeepSeek V4 enables thinking by default. For this machine-readable
|
|
1532
|
+
// contract, disable it so the output budget is reserved for the JSON
|
|
1533
|
+
// conclusion instead of an optional reasoning stream.
|
|
1534
|
+
...(isDeepSeekJsonModel(baseUrl, model) ? { thinking: { type: 'disabled' } } : {}),
|
|
1535
|
+
// DeepSeek documents that JSON Output may occasionally return an
|
|
1536
|
+
// empty content field. Retry once without provider JSON mode; the
|
|
1537
|
+
// same exact local schema validator still gates the conclusion.
|
|
1538
|
+
...(repairAttempt ? {} : { response_format: { type: 'json_object' } }),
|
|
1539
|
+
// Keep an explicit output budget so the JSON conclusion is not
|
|
1540
|
+
// truncated by a provider default.
|
|
1541
|
+
max_tokens: DEFAULT_LLM_MAX_TOKENS,
|
|
1542
|
+
}),
|
|
1543
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1544
|
+
});
|
|
1545
|
+
if (response.status === 404)
|
|
1546
|
+
continue endpointLoop;
|
|
1547
|
+
if (!response.ok) {
|
|
1548
|
+
return {
|
|
1549
|
+
taskId: task.id,
|
|
1550
|
+
status: 'failed',
|
|
1551
|
+
error: `LLM endpoint returned HTTP ${response.status}: ${safeLlmEndpoint(endpoint)}`,
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const body = await response.json();
|
|
1555
|
+
const content = body.choices?.[0]?.message?.content;
|
|
1556
|
+
let conclusionError;
|
|
1557
|
+
if (typeof content !== 'string') {
|
|
1558
|
+
conclusionError = 'LLM response had no message content';
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
try {
|
|
1562
|
+
const parsed = parseAgentOutput(extractJsonObject(content));
|
|
1563
|
+
if (parsed.value !== undefined) {
|
|
1564
|
+
return { taskId: task.id, status: 'succeeded', parsedOutput: parsed.value };
|
|
1565
|
+
}
|
|
1566
|
+
conclusionError = parsed.error ?? 'LLM returned an invalid conclusion';
|
|
1567
|
+
}
|
|
1568
|
+
catch (error) {
|
|
1569
|
+
conclusionError = safeError(error);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
if (!repairAttempt)
|
|
1573
|
+
continue;
|
|
1523
1574
|
return {
|
|
1524
1575
|
taskId: task.id,
|
|
1525
1576
|
status: 'failed',
|
|
1526
|
-
error: `LLM
|
|
1577
|
+
error: `LLM returned no valid conclusion after JSON-mode and plain-text attempts at ${safeLlmEndpoint(endpoint)}: ${conclusionError}`,
|
|
1527
1578
|
};
|
|
1528
1579
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
if (typeof content !== 'string') {
|
|
1532
|
-
return { taskId: task.id, status: 'failed', error: `LLM response had no message content: ${safeLlmEndpoint(endpoint)}` };
|
|
1533
|
-
}
|
|
1534
|
-
const parsed = parseAgentOutput(extractJsonObject(content));
|
|
1535
|
-
if (parsed.value === undefined) {
|
|
1536
|
-
return { taskId: task.id, status: 'failed', error: parsed.error ?? 'LLM returned an invalid conclusion' };
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
return { taskId: task.id, status: 'failed', error: `LLM request failed at ${safeLlmEndpoint(endpoint)}: ${safeError(error)}` };
|
|
1537
1582
|
}
|
|
1538
|
-
return { taskId: task.id, status: 'succeeded', parsedOutput: parsed.value };
|
|
1539
|
-
}
|
|
1540
|
-
catch (error) {
|
|
1541
|
-
return { taskId: task.id, status: 'failed', error: `LLM request failed at ${safeLlmEndpoint(endpoint)}: ${safeError(error)}` };
|
|
1542
1583
|
}
|
|
1543
1584
|
}
|
|
1544
1585
|
return {
|
|
@@ -1621,92 +1662,94 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1621
1662
|
const alignmentFindings = [];
|
|
1622
1663
|
const errors = [];
|
|
1623
1664
|
const newTasks = [];
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
// checkedAt belongs in the run report. Keep the durable observation point
|
|
1636
|
-
// byte-stable when its evidence did not change, so a no-op cron run does
|
|
1637
|
-
// not manufacture a commit from a fresh timestamp alone.
|
|
1638
|
-
nextTargets[configuredTarget.id] = snapshotEvidenceSignature(before) === snapshotEvidenceSignature(current)
|
|
1639
|
-
? before
|
|
1640
|
-
: current;
|
|
1641
|
-
if (before.alignment === undefined && current.alignment !== undefined) {
|
|
1642
|
-
// Upgrade legacy state into the IR and show its first finding once.
|
|
1643
|
-
// This is evidence for the author, not a fake upstream change and not
|
|
1644
|
-
// a reason to wake the Agent on every subsequent scheduled run.
|
|
1645
|
-
alignmentFindings.push({ targetId: configuredTarget.id, alignment: structuredClone(current.alignment) });
|
|
1646
|
-
}
|
|
1647
|
-
const sourceChange = before.source.commit === current.source.commit
|
|
1648
|
-
? {
|
|
1649
|
-
beforeCommit: before.source.commit,
|
|
1650
|
-
afterCommit: current.source.commit,
|
|
1651
|
-
comparison: 'complete',
|
|
1652
|
-
changedFiles: [],
|
|
1653
|
-
runtimeFiles: [],
|
|
1654
|
-
nonRuntimeFiles: [],
|
|
1665
|
+
if (options.retryPendingOnly !== true) {
|
|
1666
|
+
for (const configuredTarget of config.targets) {
|
|
1667
|
+
try {
|
|
1668
|
+
const current = await source.observe(configuredTarget, checkedAt);
|
|
1669
|
+
const before = previousState.targets[configuredTarget.id];
|
|
1670
|
+
if (before === undefined) {
|
|
1671
|
+
nextTargets[configuredTarget.id] = current;
|
|
1672
|
+
baselineTargets.push(configuredTarget.id);
|
|
1673
|
+
if (current.alignment !== undefined)
|
|
1674
|
+
alignmentFindings.push({ targetId: configuredTarget.id, alignment: structuredClone(current.alignment) });
|
|
1675
|
+
continue;
|
|
1655
1676
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
};
|
|
1677
|
+
// checkedAt belongs in the run report. Keep the durable observation point
|
|
1678
|
+
// byte-stable when its evidence did not change, so a no-op cron run does
|
|
1679
|
+
// not manufacture a commit from a fresh timestamp alone.
|
|
1680
|
+
nextTargets[configuredTarget.id] = snapshotEvidenceSignature(before) === snapshotEvidenceSignature(current)
|
|
1681
|
+
? before
|
|
1682
|
+
: current;
|
|
1683
|
+
if (before.alignment === undefined && current.alignment !== undefined) {
|
|
1684
|
+
// Upgrade legacy state into the IR and show its first finding once.
|
|
1685
|
+
// This is evidence for the author, not a fake upstream change and not
|
|
1686
|
+
// a reason to wake the Agent on every subsequent scheduled run.
|
|
1687
|
+
alignmentFindings.push({ targetId: configuredTarget.id, alignment: structuredClone(current.alignment) });
|
|
1668
1688
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1689
|
+
const sourceChange = before.source.commit === current.source.commit
|
|
1690
|
+
? {
|
|
1691
|
+
beforeCommit: before.source.commit,
|
|
1692
|
+
afterCommit: current.source.commit,
|
|
1693
|
+
comparison: 'complete',
|
|
1694
|
+
changedFiles: [],
|
|
1695
|
+
runtimeFiles: [],
|
|
1696
|
+
nonRuntimeFiles: [],
|
|
1697
|
+
}
|
|
1698
|
+
: await source.compare(current.source.repository, before.source.commit, current.source.commit);
|
|
1699
|
+
const change = createChange(configuredTarget, before, current, sourceChange, options.reverseDependencyIndex);
|
|
1700
|
+
if (change.reasons.length === 0)
|
|
1701
|
+
continue;
|
|
1702
|
+
let reviewedChange = change;
|
|
1703
|
+
if (change.meaningful && current.package !== undefined && options.artifactReviewer !== undefined) {
|
|
1704
|
+
const packageSpec = `npm:${current.package.name}@${current.package.version}`;
|
|
1705
|
+
try {
|
|
1706
|
+
reviewedChange = {
|
|
1707
|
+
...change,
|
|
1708
|
+
artifactReview: await options.artifactReviewer(packageSpec, configuredTarget),
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
catch (error) {
|
|
1712
|
+
// Artifact review is useful evidence, but it must not turn a valid
|
|
1713
|
+
// source observation into a source-observation error. Keep the
|
|
1714
|
+
// change and make the incomplete review explicit for the author and
|
|
1715
|
+
// for a later retry of the whole scheduled run.
|
|
1716
|
+
reviewedChange = {
|
|
1717
|
+
...change,
|
|
1718
|
+
artifactReview: {
|
|
1719
|
+
spec: packageSpec,
|
|
1720
|
+
verdict: 'review',
|
|
1721
|
+
riskVerdict: 'review',
|
|
1722
|
+
coverageVerdict: 'incomplete',
|
|
1723
|
+
artifactIntegrity: 'not-checked',
|
|
1724
|
+
registrySignature: 'not-checked',
|
|
1725
|
+
provenance: 'not-checked',
|
|
1726
|
+
dependencyResolution: 'manifest-only',
|
|
1727
|
+
dependencyAuditStatus: 'failed',
|
|
1728
|
+
packages: null,
|
|
1729
|
+
unresolved: 0,
|
|
1730
|
+
vulnerabilities: null,
|
|
1731
|
+
installScriptPackages: [],
|
|
1732
|
+
installScriptDetails: [],
|
|
1733
|
+
findings: [],
|
|
1734
|
+
error: safeError(error),
|
|
1735
|
+
},
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1695
1738
|
}
|
|
1739
|
+
changes.push(reviewedChange);
|
|
1740
|
+
if (!reviewedChange.meaningful || reviewedChange.taskId === undefined)
|
|
1741
|
+
continue;
|
|
1742
|
+
const task = createUpstreamChangeTask(configuredTarget, reviewedChange, checkedAt);
|
|
1743
|
+
newTasks.push(task);
|
|
1744
|
+
if (!taskAlreadyPending(pendingTasks, task.id))
|
|
1745
|
+
pendingTasks.push(task);
|
|
1746
|
+
}
|
|
1747
|
+
catch (error) {
|
|
1748
|
+
errors.push({ targetId: configuredTarget.id, message: safeError(error) });
|
|
1696
1749
|
}
|
|
1697
|
-
changes.push(reviewedChange);
|
|
1698
|
-
if (!reviewedChange.meaningful || reviewedChange.taskId === undefined)
|
|
1699
|
-
continue;
|
|
1700
|
-
const task = createUpstreamChangeTask(configuredTarget, reviewedChange, checkedAt);
|
|
1701
|
-
newTasks.push(task);
|
|
1702
|
-
if (!taskAlreadyPending(pendingTasks, task.id))
|
|
1703
|
-
pendingTasks.push(task);
|
|
1704
|
-
}
|
|
1705
|
-
catch (error) {
|
|
1706
|
-
errors.push({ targetId: configuredTarget.id, message: safeError(error) });
|
|
1707
1750
|
}
|
|
1708
1751
|
}
|
|
1709
|
-
const pendingToRetry = options.retryPending === true
|
|
1752
|
+
const pendingToRetry = options.retryPending === true || options.retryPendingOnly === true
|
|
1710
1753
|
? pendingTasks.filter(task => !newTasks.some(current => current.id === task.id))
|
|
1711
1754
|
: [];
|
|
1712
1755
|
const tasksToInvoke = [...newTasks, ...pendingToRetry];
|
|
@@ -1721,7 +1764,7 @@ export async function runObserver(config, previousState, options = {}) {
|
|
|
1721
1764
|
const report = {
|
|
1722
1765
|
schema: OBSERVER_REPORT_SCHEMA,
|
|
1723
1766
|
checkedAt,
|
|
1724
|
-
targetsChecked: config.targets.length,
|
|
1767
|
+
targetsChecked: options.retryPendingOnly === true ? 0 : config.targets.length,
|
|
1725
1768
|
baselineTargets,
|
|
1726
1769
|
alignmentFindings,
|
|
1727
1770
|
...(options.reverseDependencyIndex === undefined ? {} : {
|