@jjh32456/dsh-longlive 1.6.7 → 1.8.0
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/CHANGELOG.md +70 -1508
- package/LICENSE +1 -1
- package/QUICKSTART.md +34 -13
- package/README.en.md +18 -7
- package/README.md +34 -9
- package/SPEC.md +5 -5
- package/cordis.patch.yml +23 -9
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +168 -35
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +96 -28
- package/docs/MIGRATION-1.6.0.md +2 -2
- package/docs/OPERATIONS.md +6 -6
- package/docs/PANEL-INTEGRATION.md +6 -0
- package/docs/PRIVACY.md +88 -0
- package/docs/QUICKSTART.md +5 -5
- package/docs/README.en.md +4 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +47 -117
- package/runbook.md +2 -2
- package/scripts/backfill-auto-cards.mjs +98 -0
- package/scripts/install-surgery-task.ps1 +5 -2
- package/scripts/lib/session-watch-fixture.mjs +1 -1
- package/scripts/longlive-doctor.mjs +6 -0
- package/scripts/migrate-180.mjs +163 -0
- package/scripts/recall-index-build.mjs +128 -0
- package/scripts/session-watch-drill.mjs +1 -1
- package/scripts/surgery-run.cmd +1 -1
- package/scripts/unattended-surgery.ps1 +5 -2
- package/scripts/verify-card-anchors.mjs +138 -0
- package/src/actions/audit.js +125 -0
- package/src/actions/evidence.js +122 -0
- package/src/actions/fs.js +162 -0
- package/src/activity/index.js +760 -0
- package/src/activity/model.js +351 -0
- package/src/activity/store.js +644 -0
- package/src/activity/thinking.js +176 -0
- package/src/adapters/constants.js +11 -0
- package/src/adapters/engram/data/aliases.json +51 -0
- package/src/adapters/engram/data/policy.json +8 -0
- package/src/adapters/engram/data/routing-rules.json +546 -0
- package/src/adapters/engram/index.js +235 -0
- package/src/adapters/registry.js +315 -0
- package/src/answer/audit.js +193 -0
- package/src/answer/evidence.js +30 -0
- package/src/answer/guard.js +1370 -0
- package/src/answer/index.js +1067 -0
- package/src/answer/query.js +111 -0
- package/src/answer/second-hop.mjs +101 -0
- package/src/auto-truncate.js +1 -1
- package/src/config.js +171 -47
- package/src/deps.js +6 -4
- package/src/features.js +182 -6
- package/src/heartbeat.js +156 -23
- package/src/identity/handover.js +52 -1
- package/src/identity/index.js +13 -5
- package/src/identity/inject-position.js +2 -2
- package/src/index.js +97 -7
- package/src/lifecycle/gap-clocks.js +328 -0
- package/src/lifecycle/index.js +269 -0
- package/src/live.js +22 -2
- package/src/memory/activity-capture.js +229 -0
- package/src/memory/anchors.js +194 -0
- package/src/memory/audit-records.js +222 -0
- package/src/memory/auto-cards.js +401 -0
- package/src/memory/capture.js +442 -0
- package/src/memory/card-anchors.js +203 -0
- package/src/memory/conflicts.js +106 -0
- package/src/memory/decay.js +311 -0
- package/src/memory/distill.js +471 -0
- package/src/memory/event-index.js +917 -0
- package/src/memory/evidence-chain.js +295 -0
- package/src/memory/evidence.js +145 -0
- package/src/memory/filter-runner.js +1 -1
- package/src/memory/filter.js +5 -1
- package/src/memory/forget-v2.js +410 -0
- package/src/memory/forget.js +71 -0
- package/src/memory/frames-activation.js +247 -0
- package/src/memory/fts-query.js +174 -0
- package/src/memory/governance.js +604 -0
- package/src/memory/index.js +1 -0
- package/src/memory/l2-gate.js +169 -0
- package/src/memory/lock.js +121 -0
- package/src/memory/migrate.js +139 -0
- package/src/memory/recall-v2.js +223 -0
- package/src/memory/recall.js +65 -2
- package/src/memory/records.js +425 -0
- package/src/memory/retention.js +128 -0
- package/src/memory/review.js +124 -0
- package/src/memory/trust.js +88 -0
- package/src/memory/version-chain.js +264 -0
- package/src/memory/wal.js +137 -0
- package/src/memory/working-frame.js +516 -0
- package/src/metrics/classify.mjs +5 -3
- package/src/metrics/index.mjs +21 -1
- package/src/metrics/prestep-latency.mjs +300 -0
- package/src/metrics/store.mjs +25 -0
- package/src/policy-profile.js +18 -3
- package/src/recall/anaphora.mjs +87 -0
- package/src/recall/cards.mjs +7 -0
- package/src/recall/embedding.mjs +8 -8
- package/src/recall/entities.mjs +112 -0
- package/src/recall/index.js +235 -19
- package/src/recall/inject-order.mjs +10 -8
- package/src/recall/privacy.mjs +105 -16
- package/src/recall/recall-core.mjs +116 -10
- package/src/recall/shared-visibility.js +302 -0
- package/src/recall/time-normalizer.mjs +196 -0
- package/src/role/activation.js +191 -0
- package/src/role/attention.js +318 -0
- package/src/role/base.js +36 -0
- package/src/role/channels.js +423 -0
- package/src/role/index.js +425 -0
- package/src/role/interfaces.js +115 -0
- package/src/role/personality.js +370 -0
- package/src/role/readers.js +151 -0
- package/src/role/relationships.js +477 -0
- package/src/role/store.js +202 -0
- package/src/role/writeprotect.js +373 -0
- package/src/router/aliases.json +176 -25
- package/src/router/background/agent-runner.mjs +1 -1
- package/src/router/background/anchor.mjs +1 -1
- package/src/router/background/audit.mjs +2 -2
- package/src/router/background/budget-ledger.mjs +2 -2
- package/src/router/background/host-sim.mjs +1 -1
- package/src/router/background/memory.mjs +4 -4
- package/src/router/background/queue.mjs +1 -1
- package/src/router/background/retry.mjs +1 -1
- package/src/router/background/router.mjs +1 -1
- package/src/router/background/stream.mjs +1 -1
- package/src/router/background/tool-policy.mjs +1 -1
- package/src/router/design-headings.json +5 -5
- package/src/router/face/config.mjs +1 -1
- package/src/router/face/identity.mjs +3 -3
- package/src/router/judge.mjs +8 -7
- package/src/router/policies/generic.json +21 -4
- package/src/router/route.js +109 -22
- package/src/router/router.mjs +10 -6
- package/src/router/routing-rules.json +260 -445
- package/src/router/run-sim.mjs +8 -8
- package/src/router/runtime.js +23 -12
- package/src/router/service/board.mjs +95 -15
- package/src/router/service/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/hint.mjs +14 -0
- package/src/router/service/queue.mjs +1 -1
- package/src/router/service/service.mjs +27 -11
- package/src/router/shadow.mjs +4 -3
- package/src/surgery-recovery.js +2 -2
- package/src/tool-output.js +2 -2
- package/src/tools.js +39 -7
- package/src/truncate-visible.js +2 -2
- package/src/ui/actions.js +1312 -0
- package/src/ui/panel-api.js +367 -0
- package/src/ui/panel-auth.js +251 -0
- package/src/ui/panel-integrity.js +243 -0
- package/src/ui/routes-readonly.js +695 -0
- package/src/ui/routes.js +3 -0
- package/docs/DEV.md +0 -279
- package/docs/PRESENCE-BUS-WIRING.md +0 -491
- package/docs/PRESENCE-BUS.md +0 -694
- package/docs/SURGERY.md +0 -186
- package/scripts/mark-core-cards.mjs +0 -289
- package/scripts/pack.mjs +0 -392
- package/scripts/router-proposals.mjs +0 -386
- package/src/router/fixtures/M1.json +0 -16
- package/src/router/fixtures/M2.json +0 -16
- package/src/router/fixtures/M3.json +0 -20
- package/src/router/fixtures/M4.json +0 -20
- package/src/router/fixtures/M5.json +0 -45
- package/src/router/fixtures/M6.json +0 -53
- package/src/router/fixtures/M7.json +0 -46
- package/src/router/fixtures/S1.json +0 -20
- package/src/router/fixtures/S2.json +0 -23
- package/src/router/fixtures/S3.json +0 -20
- package/src/router/fixtures/S4.json +0 -34
- package/src/router/fixtures/S5.json +0 -43
- package/src/router/fixtures/S6.json +0 -47
- package/src/router/fixtures/T1.json +0 -23
- package/src/router/fixtures/T2.json +0 -16
- package/src/router/fixtures/T3.json +0 -19
- package/src/router/fixtures/T4.json +0 -16
- package/src/router/fixtures/X1.json +0 -17
- package/src/router/fixtures/X2.json +0 -20
- package/src/router/gen-design-headings.mjs +0 -107
- package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
- package/test/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -385
- package/test/bus.test.mjs +0 -1572
- package/test/cli-guard.test.mjs +0 -198
- package/test/conclusion.test.mjs +0 -408
- package/test/config-schema.test.mjs +0 -204
- package/test/d6-output.test.mjs +0 -194
- package/test/d6.test.mjs +0 -395
- package/test/deps.test.mjs +0 -342
- package/test/doctor-setup.test.mjs +0 -297
- package/test/embedding-recall.test.mjs +0 -347
- package/test/feature-flags.test.mjs +0 -568
- package/test/filter-shadow.test.mjs +0 -170
- package/test/fixtures/baseline-0.8.0-registration.json +0 -556
- package/test/fixtures/d6-header-tools.json +0 -1
- package/test/fixtures/lock-child.mjs +0 -67
- package/test/fixtures/pre-step-container.mjs +0 -100
- package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
- package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
- package/test/fixtures/profile-session-longlive-config.json +0 -20
- package/test/fixtures/proposal-cas-child.mjs +0 -66
- package/test/host-engine-status.test.mjs +0 -307
- package/test/hot-truncate.test.mjs +0 -1349
- package/test/identity-card-marker.test.mjs +0 -307
- package/test/identity-router/run.mjs +0 -721
- package/test/integration-pre-step.test.mjs +0 -553
- package/test/live.test.mjs +0 -1020
- package/test/maintenance-schedule.test.mjs +0 -538
- package/test/memory.test.mjs +0 -2828
- package/test/metrics.test.mjs +0 -478
- package/test/orchestrator.test.mjs +0 -266
- package/test/p1_probe.mjs +0 -97
- package/test/packaging.test.mjs +0 -182
- package/test/portability.test.mjs +0 -343
- package/test/rc4-regressions.test.mjs +0 -562
- package/test/rc5-regressions.test.mjs +0 -197
- package/test/rc6-chinese.test.mjs +0 -394
- package/test/router-1100/apply-off.test.mjs +0 -175
- package/test/router-1100/budget-reconcile.test.mjs +0 -215
- package/test/router-1100/config-context.test.mjs +0 -211
- package/test/router-1100/helpers.mjs +0 -129
- package/test/router-1100/hint-envelope.test.mjs +0 -150
- package/test/router-1100/lock-crossproc.test.mjs +0 -206
- package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
- package/test/router-1100/router-apply.test.mjs +0 -372
- package/test/router-1100/router-fixb.test.mjs +0 -602
- package/test/router-1100/router-proposals.test.mjs +0 -405
- package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
- package/test/router-1100/router-security-cr.test.mjs +0 -333
- package/test/router-1100/service-lifecycle.test.mjs +0 -220
- package/test/router-1100/tools-contract.test.mjs +0 -164
- package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
- package/test/router-1100/v6-contract.test.mjs +0 -158
- package/test/router-1100/v6b-contract.test.mjs +0 -293
- package/test/router-1100/v6c-contract.test.mjs +0 -249
- package/test/router-1100/wal-rotation.test.mjs +0 -375
- package/test/router-1100/wp1-config.test.mjs +0 -170
- package/test/router-1100/wp1-default-off.test.mjs +0 -77
- package/test/router-1100/wp1-model-status.test.mjs +0 -90
- package/test/router-1100/wp1-status.test.mjs +0 -220
- package/test/router-1100/wp1-tools.test.mjs +0 -146
- package/test/router-1100/wp5-cli.test.mjs +0 -240
- package/test/router-1100/wp5-model-auto.test.mjs +0 -418
- package/test/router-1100/wp6-merge.test.mjs +0 -386
- package/test/router-1100/wp6-wake.test.mjs +0 -502
- package/test/router-background/audit.test.mjs +0 -37
- package/test/router-background/budget-ledger.test.mjs +0 -330
- package/test/router-background/cross-process.test.mjs +0 -222
- package/test/router-background/fixtures/cross-child.mjs +0 -85
- package/test/router-background/fixtures/real-shapes.mjs +0 -239
- package/test/router-background/router.test.mjs +0 -1044
- package/test/router-background/stream-retry.test.mjs +0 -358
- package/test/router-background/tool-agent.test.mjs +0 -384
- package/test/router-face/bridge.test.mjs +0 -169
- package/test/router-face/face.test.mjs +0 -316
- package/test/router-failclosed.test.mjs +0 -466
- package/test/router-identity.test.mjs +0 -384
- package/test/router-l1-l2.contract.test.mjs +0 -1075
- package/test/router-memory-tools.test.mjs +0 -262
- package/test/router-policy.test.mjs +0 -225
- package/test/router-runtime.test.mjs +0 -438
- package/test/router-shadow.test.mjs +0 -287
- package/test/router.test.mjs +0 -903
- package/test/run-rc.mjs +0 -299
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -179
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- package/test/surgery-recovery.test.mjs +0 -936
- package/test/t7_drive.mjs +0 -151
- package/test/tool-output.test.mjs +0 -393
- package/test/tool-schema.test.mjs +0 -177
- package/test/tools_contract.test.mjs +0 -716
- package/test/truncate-visible.test.mjs +0 -1150
- package/test/ui-flags.test.mjs +0 -477
- package/test/v4-hardening.test.mjs +0 -427
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
// suite: host-visible Config schema (1.5.0, UI-config batch)
|
|
2
|
-
//
|
|
3
|
-
// Contract under test:
|
|
4
|
-
// - the exact config block from the live desktop profile's
|
|
5
|
-
// `- id: session-longlive` row parses without losing a value;
|
|
6
|
-
// - every declared default equals the value the 1.5.0 runtime already
|
|
7
|
-
// falls back to (compared against the normalisers, not re-typed copies);
|
|
8
|
-
// - absent fields fall back, unknown keys are preserved (schemastery objects
|
|
9
|
-
// merge unknown members in non-strict mode), and non-boolean switch values
|
|
10
|
-
// are rejected by `~standard.validate` exactly as Cordis calls it.
|
|
11
|
-
//
|
|
12
|
-
// The fixture is a verbatim copy of the live desktop profile's
|
|
13
|
-
// `- id: session-longlive` config block (cordis.patch.yml lines 47-64), read
|
|
14
|
-
// on 2026-11-06. It has 10 top-level keys / 10 leaf settings (the task note
|
|
15
|
-
// said 11; the file is the authority).
|
|
16
|
-
|
|
17
|
-
import test from 'node:test';
|
|
18
|
-
import assert from 'node:assert/strict';
|
|
19
|
-
import fs from 'node:fs';
|
|
20
|
-
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
|
|
23
|
-
import { Config, CONFIG_SCHEMA_AVAILABLE } from '../src/config.js';
|
|
24
|
-
import { D2_DEFAULTS, normalizeD2Config } from '../src/tool-output.js';
|
|
25
|
-
import { FEATURE_DEFAULTS, normalizeFeatureConfig } from '../src/features.js';
|
|
26
|
-
import { FACE_CONFIG_KEYS, normalizeFaceConfig } from '../src/router/face/config.mjs';
|
|
27
|
-
import { AUTO_TRUNCATE_DEFAULTS, normalizeAutoTruncateConfig } from '../src/auto-truncate.js';
|
|
28
|
-
|
|
29
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const ROOT = path.resolve(HERE, '..');
|
|
31
|
-
const FIXTURE = path.join(ROOT, 'test', 'fixtures', 'profile-session-longlive-config.json');
|
|
32
|
-
|
|
33
|
-
const skip = CONFIG_SCHEMA_AVAILABLE
|
|
34
|
-
? false
|
|
35
|
-
: 'schemastery 不可解析:安装 peer 或设置 NODE_PATH 指向含 @deepseek-ai/schemastery 的 node_modules';
|
|
36
|
-
|
|
37
|
-
/** The config block the shipped cordis.patch.yml inserts (read from the work copy). */
|
|
38
|
-
const SHIPPED_PATCH_CONFIG = {
|
|
39
|
-
keepEvents: 20000,
|
|
40
|
-
recentWriteWindowMs: 60000,
|
|
41
|
-
routerPolicyProfile: 'generic',
|
|
42
|
-
surgeryRecovery: true,
|
|
43
|
-
routerRuntime: 'off',
|
|
44
|
-
workers: { enabled: false },
|
|
45
|
-
autoTruncate: { enabled: false, mode: 'dryrun' },
|
|
46
|
-
filterEnabled: true,
|
|
47
|
-
shadowEnabled: true,
|
|
48
|
-
autoRecall: { enabled: false, topK: 3, budgetTokens: 2000, everyNTurns: 6 },
|
|
49
|
-
identityCard: { enabled: false, path: '<memoryRoot>/STATE.md' },
|
|
50
|
-
presenceBus: { enabled: false, pollMs: 3000 },
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
test('1.6.0 真实 profile 的 session-longlive config 原样通过校验', { skip }, () => {
|
|
54
|
-
const fixture = JSON.parse(fs.readFileSync(FIXTURE, 'utf8'));
|
|
55
|
-
assert.equal(Object.keys(fixture).length, 10, 'fixture 是 profile 的 10 个顶层键的逐字副本');
|
|
56
|
-
const parsed = Config(fixture);
|
|
57
|
-
for (const [key, value] of Object.entries(fixture)) {
|
|
58
|
-
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
59
|
-
for (const [child, childValue] of Object.entries(value)) {
|
|
60
|
-
assert.equal(parsed[key][child], childValue, `${key}.${child} 必须原样保留`);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
assert.equal(parsed[key], value, `${key} 必须原样保留`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// The schema fills the documented sub-defaults even when the profile only
|
|
67
|
-
// wrote the switch.
|
|
68
|
-
assert.deepEqual(parsed.d2, {
|
|
69
|
-
enabled: true,
|
|
70
|
-
maxInlineChars: D2_DEFAULTS.maxInlineChars,
|
|
71
|
-
headRatio: D2_DEFAULTS.headRatio,
|
|
72
|
-
skipTools: [...D2_DEFAULTS.skipTools],
|
|
73
|
-
trimErrors: D2_DEFAULTS.trimErrors,
|
|
74
|
-
});
|
|
75
|
-
assert.equal(parsed.autoRecall.topK, 3);
|
|
76
|
-
assert.equal(parsed.autoRecall.budgetTokens, 2000);
|
|
77
|
-
assert.equal(parsed.autoRecall.everyNTurns, 6);
|
|
78
|
-
assert.equal(parsed.presenceBus.pollMs, 3000);
|
|
79
|
-
assert.equal(parsed.routerRuntime, 'shadow');
|
|
80
|
-
assert.equal(parsed.routerProvider, 'deepseek-account');
|
|
81
|
-
assert.equal(parsed.routerModel, 'auto');
|
|
82
|
-
assert.equal(parsed.routerSharedBoard, true);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('缺省字段回落到 1.6.0 运行时的默认值(对照归一化器,不重复抄常量)', { skip }, () => {
|
|
86
|
-
const parsed = Config({});
|
|
87
|
-
|
|
88
|
-
assert.deepEqual(parsed.d2, normalizeD2Config(undefined), 'd2 默认值必须等于 normalizeD2Config(undefined)');
|
|
89
|
-
assert.deepEqual(parsed.d6, {
|
|
90
|
-
enabled: false,
|
|
91
|
-
dryRun: false,
|
|
92
|
-
log: true,
|
|
93
|
-
keepTools: [],
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const features = normalizeFeatureConfig({});
|
|
97
|
-
for (const key of ['enabled', 'topK', 'budgetTokens', 'everyNTurns']) {
|
|
98
|
-
assert.equal(parsed.autoRecall[key], features.autoRecall[key], `autoRecall.${key}`);
|
|
99
|
-
}
|
|
100
|
-
for (const key of ['enabled', 'path', 'maxChars']) {
|
|
101
|
-
assert.equal(parsed.identityCard[key], features.identityCard[key], `identityCard.${key}`);
|
|
102
|
-
}
|
|
103
|
-
for (const key of ['enabled', 'pollMs']) {
|
|
104
|
-
assert.equal(parsed.presenceBus[key], features.presenceBus[key], `presenceBus.${key}`);
|
|
105
|
-
}
|
|
106
|
-
for (const key of ['enabled', 'consolidate', 'forget', 'runOnStart', 'startDelayMs']) {
|
|
107
|
-
assert.equal(parsed.autoMaintenance[key], features.autoMaintenance[key], `autoMaintenance.${key}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const face = normalizeFaceConfig(undefined);
|
|
111
|
-
for (const key of FACE_CONFIG_KEYS) {
|
|
112
|
-
assert.equal(parsed.routerFace[key], face[key], `routerFace.${key}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
assert.deepEqual(parsed.autoTruncate, normalizeAutoTruncateConfig(undefined));
|
|
116
|
-
|
|
117
|
-
// The fields that intentionally have no schema default because the runtime
|
|
118
|
-
// distinguishes "absent" from a value.
|
|
119
|
-
assert.equal(parsed.routerRuntime, undefined);
|
|
120
|
-
assert.equal(parsed.routerProvider, undefined);
|
|
121
|
-
assert.equal(parsed.memoryRoot, undefined);
|
|
122
|
-
assert.equal(parsed.routerProposalEnabled, undefined);
|
|
123
|
-
assert.equal(parsed.routerL4MergeEnabled, undefined);
|
|
124
|
-
assert.equal(parsed.routerWriterLeaseMs, undefined);
|
|
125
|
-
|
|
126
|
-
assert.equal(parsed.routerEnabled, false);
|
|
127
|
-
assert.equal(parsed.routerSharedBoard, false);
|
|
128
|
-
assert.equal(parsed.routerModel, 'auto');
|
|
129
|
-
assert.equal(parsed.routerPolicyProfile, 'generic');
|
|
130
|
-
assert.equal(parsed.filterEnabled, true);
|
|
131
|
-
assert.equal(parsed.shadowEnabled, true);
|
|
132
|
-
assert.equal(parsed.surgeryRecovery, true);
|
|
133
|
-
assert.equal(parsed.recoverOnStart, true);
|
|
134
|
-
assert.equal(parsed.autoConclusion, false);
|
|
135
|
-
assert.equal(parsed.keepEvents, 20000);
|
|
136
|
-
assert.equal(parsed.recentWriteWindowMs, 60000);
|
|
137
|
-
assert.equal(parsed.heartbeatMs, 15000);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('发布包 cordis.patch.yml 的 config 也通过校验', { skip }, () => {
|
|
141
|
-
const parsed = Config(SHIPPED_PATCH_CONFIG);
|
|
142
|
-
assert.equal(parsed.routerRuntime, 'off');
|
|
143
|
-
assert.equal(parsed.workers.enabled, false);
|
|
144
|
-
assert.equal(parsed.autoTruncate.enabled, false);
|
|
145
|
-
assert.equal(parsed.autoTruncate.mode, 'dryrun');
|
|
146
|
-
assert.equal(parsed.autoTruncate.keepTurns, AUTO_TRUNCATE_DEFAULTS.keepTurns);
|
|
147
|
-
assert.equal(parsed.autoTruncate.keepTokens, AUTO_TRUNCATE_DEFAULTS.keepTokens);
|
|
148
|
-
assert.deepEqual(parsed.autoRecall, SHIPPED_PATCH_CONFIG.autoRecall);
|
|
149
|
-
assert.deepEqual(parsed.identityCard, { ...SHIPPED_PATCH_CONFIG.identityCard, maxChars: 800 });
|
|
150
|
-
assert.equal(parsed.presenceBus.enabled, false);
|
|
151
|
-
assert.equal(parsed.presenceBus.pollMs, 3000);
|
|
152
|
-
assert.equal('root' in parsed.presenceBus, false, '未配置的可选字段不应被凭空写出');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('未知键不被拒绝:顶层、嵌套块与测试注入口都保留', { skip }, () => {
|
|
156
|
-
const now = () => new Date(0);
|
|
157
|
-
const parsed = Config({
|
|
158
|
-
now,
|
|
159
|
-
futureTopLevel: { flag: 1 },
|
|
160
|
-
d2: { enabled: false, futureSubKey: 'kept' },
|
|
161
|
-
});
|
|
162
|
-
assert.equal(parsed.now, now, '测试注入的函数必须原样穿过 Config');
|
|
163
|
-
assert.deepEqual(parsed.futureTopLevel, { flag: 1 });
|
|
164
|
-
assert.equal(parsed.d2.futureSubKey, 'kept');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('开关字段类型校验:非布尔被拒绝(~standard 与直接调用一致)', { skip }, () => {
|
|
168
|
-
for (const config of [
|
|
169
|
-
{ d2: { enabled: 'true' } },
|
|
170
|
-
{ d6: { enabled: 1 } },
|
|
171
|
-
{ autoRecall: { enabled: 'yes' } },
|
|
172
|
-
{ presenceBus: { enabled: false, pollMs: 'soon' } },
|
|
173
|
-
{ autoMaintenance: { enabled: 'yes' } },
|
|
174
|
-
{ routerFace: { enabled: 'on' } },
|
|
175
|
-
{ routerEnabled: 'true' },
|
|
176
|
-
]) {
|
|
177
|
-
assert.throws(() => Config(config), /expected (boolean|number)/, JSON.stringify(config));
|
|
178
|
-
const result = Config['~standard'].validate(config);
|
|
179
|
-
assert.ok(Array.isArray(result.issues) && result.issues.length > 0,
|
|
180
|
-
`${JSON.stringify(config)} 必须被 ~standard.validate 拒绝`);
|
|
181
|
-
}
|
|
182
|
-
const accepted = Config['~standard'].validate({ d2: { enabled: true } });
|
|
183
|
-
assert.equal(accepted.issues, undefined);
|
|
184
|
-
assert.equal(accepted.value.d2.enabled, true);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('schema 面:可 toJSON、描述同时有 zh/en、分组可折叠', { skip }, () => {
|
|
188
|
-
assert.equal(typeof Config, 'function');
|
|
189
|
-
assert.equal(Config['~standard'].version, 1);
|
|
190
|
-
assert.equal(typeof Config.toJSON, 'function');
|
|
191
|
-
const json = Config.toJSON();
|
|
192
|
-
assert.ok(json && json.refs, 'toJSON 必须返回 schemastery 的 refs 信封');
|
|
193
|
-
|
|
194
|
-
const d2Enabled = Config.dict.d2.dict.enabled;
|
|
195
|
-
assert.equal(d2Enabled.meta.description.zh, '开启 D2 工具输出裁剪(默认关闭)。');
|
|
196
|
-
assert.equal(d2Enabled.meta.description.en, 'Enable D2 tool-output trimming (off by default).');
|
|
197
|
-
assert.equal(d2Enabled.meta.description[''], '开启 D2 工具输出裁剪(默认关闭)。');
|
|
198
|
-
assert.equal(Config.dict.d2.meta.collapse, true, '嵌套块应可折叠');
|
|
199
|
-
assert.equal(Config.dict.d6.meta.collapse, true);
|
|
200
|
-
assert.equal(Config.dict.routerFace.meta.collapse, true);
|
|
201
|
-
|
|
202
|
-
assert.equal(Config.dict.memoryRoot.meta.disabled, true, 'memoryRoot 是只读展示字段');
|
|
203
|
-
assert.equal(Config.dict.memRoot.meta.disabled, true);
|
|
204
|
-
});
|
package/test/d6-output.test.mjs
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// D6 tool_search result-content gate.
|
|
2
|
-
//
|
|
3
|
-
// Host contract (@deepseek-ai/dsh-tools 0.2.2-rc.1, pipeline.createSuccessResult):
|
|
4
|
-
// the return value of `output.render(args, value)` is used VERBATIM as the
|
|
5
|
-
// tool-result message `content`. The model layer then walks that content as
|
|
6
|
-
// typed blocks: @deepseek-ai/dsh-llm contentHasImage() does
|
|
7
|
-
//
|
|
8
|
-
// content.some((block) => block.type === 'image')
|
|
9
|
-
//
|
|
10
|
-
// so a string makes the host throw `content.some is not a function` — exactly
|
|
11
|
-
// the 1.5.0-rc.2 VM recheck failure (REPORT.md item 3).
|
|
12
|
-
//
|
|
13
|
-
// rc.2's TOOL_SEARCH_SPEC.output.render returned JSON.stringify(value); the
|
|
14
|
-
// other 22 tools in src/tools.js render through OUTPUT_TEXT ->
|
|
15
|
-
// [{ type: 'text', text }]. This suite pins tool_search to that host shape and
|
|
16
|
-
// proves the legacy string shape fails the same walk.
|
|
17
|
-
|
|
18
|
-
import assert from 'node:assert/strict';
|
|
19
|
-
import test from 'node:test';
|
|
20
|
-
|
|
21
|
-
import { TOOL_SEARCH_SPEC, registerD6 } from '../src/d6/runtime.js';
|
|
22
|
-
import { registerTools } from '../src/tools.js';
|
|
23
|
-
|
|
24
|
-
function tool(name, description) {
|
|
25
|
-
return { name, description, parameters: { value: { type: 'string' } } };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const BASE = [
|
|
29
|
-
tool('read', 'Read a file from the workspace.'),
|
|
30
|
-
tool('write', 'Write a file in the workspace.'),
|
|
31
|
-
tool('edit', 'Edit a text file by replacing literal text.'),
|
|
32
|
-
tool('pwsh', 'Run a PowerShell command.'),
|
|
33
|
-
tool('glob', 'Find files by glob pattern.'),
|
|
34
|
-
tool('grep', 'Search file contents with a regex.'),
|
|
35
|
-
tool('todo_write', 'Record a task list.'),
|
|
36
|
-
tool('session_health', 'Inspect a DSH session log.'),
|
|
37
|
-
tool('session_search', 'Search prior sessions.'),
|
|
38
|
-
tool('memory_recall', 'Search memory cards.'),
|
|
39
|
-
tool('read_image', 'Read a PNG/JPEG/WebP/GIF image.'),
|
|
40
|
-
tool('send_meme', 'Send a meme image.'),
|
|
41
|
-
tool('learn_meme', 'Add an image to the meme library.'),
|
|
42
|
-
tool('meme_list', 'List meme images.'),
|
|
43
|
-
tool('present', 'Declare existing files as final deliverables.'),
|
|
44
|
-
tool('job_list', 'List background jobs.'),
|
|
45
|
-
tool('job_output', 'Read a background job.'),
|
|
46
|
-
tool('workflow', 'Run a JavaScript workflow of subagents.'),
|
|
47
|
-
tool('subagent', 'Delegate a task to a subagent.'),
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
const USER = (text) => ({ role: 'user', source: { kind: 'user' }, content: [{ type: 'text', text }] });
|
|
51
|
-
|
|
52
|
-
function makeHost({ config = { d6: { enabled: true } } } = {}) {
|
|
53
|
-
const defs = [...BASE, TOOL_SEARCH_SPEC];
|
|
54
|
-
const layers = [];
|
|
55
|
-
const listeners = new Map();
|
|
56
|
-
const ctx = {
|
|
57
|
-
tools: {
|
|
58
|
-
definitions() {
|
|
59
|
-
const denied = new Set(layers.flatMap((layer) => [...layer]));
|
|
60
|
-
return defs.filter((entry) => !denied.has(entry.name));
|
|
61
|
-
},
|
|
62
|
-
view() {
|
|
63
|
-
const denied = new Set(layers.flatMap((layer) => [...layer]));
|
|
64
|
-
const visible = new Map();
|
|
65
|
-
for (const entry of defs) {
|
|
66
|
-
if (!denied.has(entry.name)) visible.set(entry.name, { definition: entry });
|
|
67
|
-
}
|
|
68
|
-
return { visible, restrictableNames: new Set(defs.map((entry) => entry.name)) };
|
|
69
|
-
},
|
|
70
|
-
restrict({ deny }) {
|
|
71
|
-
const layer = new Set(deny);
|
|
72
|
-
layers.push(layer);
|
|
73
|
-
let active = true;
|
|
74
|
-
return () => {
|
|
75
|
-
if (!active) return;
|
|
76
|
-
active = false;
|
|
77
|
-
const index = layers.indexOf(layer);
|
|
78
|
-
if (index >= 0) layers.splice(index, 1);
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
on(name, listener) {
|
|
83
|
-
const list = listeners.get(name) ?? [];
|
|
84
|
-
list.push(listener);
|
|
85
|
-
listeners.set(name, list);
|
|
86
|
-
return () => {};
|
|
87
|
-
},
|
|
88
|
-
logger() {
|
|
89
|
-
return { info: () => {}, warn: () => {}, debug: () => {} };
|
|
90
|
-
},
|
|
91
|
-
effect() {},
|
|
92
|
-
};
|
|
93
|
-
const agent = { ctx, session: { snapshotEvents: () => [] } };
|
|
94
|
-
const registration = registerD6(ctx, config);
|
|
95
|
-
const emitClaimed = (message, turn = 1) => {
|
|
96
|
-
for (const listener of listeners.get('agent/inbox/claimed') ?? []) {
|
|
97
|
-
listener({ agent, message, turn });
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
return { ctx, agent, registration, emitClaimed };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Literal copy of the host walk that threw in the VM
|
|
104
|
-
// (@deepseek-ai/dsh-llm 0.2.2-rc.1 contentHasImage). Keep `content.some(...)`
|
|
105
|
-
// untouched: a string must fail here with the exact TypeError text.
|
|
106
|
-
function contentHasImage(content) {
|
|
107
|
-
return content.some((block) => block.type === 'image');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Host result-content contract: the walk above must not throw, the content must
|
|
111
|
-
// be a non-empty array, and every block must be a typed content block with a
|
|
112
|
-
// string `text` when it is a text block (the shape OUTPUT_TEXT emits).
|
|
113
|
-
function assertHostToolResultContent(content) {
|
|
114
|
-
contentHasImage(content);
|
|
115
|
-
assert.ok(Array.isArray(content), 'tool result content must be an array of content blocks');
|
|
116
|
-
assert.ok(content.length > 0, 'tool result content must contain at least one block');
|
|
117
|
-
for (const block of content) {
|
|
118
|
-
assert.ok(block !== null && typeof block === 'object' && !Array.isArray(block), 'every content block must be an object');
|
|
119
|
-
assert.equal(typeof block.type, 'string', 'every content block needs a string type');
|
|
120
|
-
if (block.type === 'text') assert.equal(typeof block.text, 'string', 'text blocks need a string text');
|
|
121
|
-
}
|
|
122
|
-
return content;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
test('tool_search renders a legal host content-block array on list and add paths', () => {
|
|
126
|
-
const host = makeHost();
|
|
127
|
-
host.emitClaimed(USER('修复这个 TypeScript 编译错误,补单元测试并跑通'));
|
|
128
|
-
const toolSearch = host.registration.tools[0];
|
|
129
|
-
assert.equal(toolSearch.name, 'tool_search');
|
|
130
|
-
|
|
131
|
-
const listed = toolSearch.invoke({}, { exec: { agent: host.agent }, host: () => host.ctx });
|
|
132
|
-
const listedContent = toolSearch.output.render({}, listed);
|
|
133
|
-
assertHostToolResultContent(listedContent);
|
|
134
|
-
assert.deepEqual(JSON.parse(listedContent[0].text), listed);
|
|
135
|
-
assert.match(listedContent[0].text, /"totalTrimmed"/);
|
|
136
|
-
|
|
137
|
-
const added = toolSearch.invoke(
|
|
138
|
-
{ names: ['read_image'] },
|
|
139
|
-
{ exec: { agent: host.agent }, host: () => host.ctx },
|
|
140
|
-
);
|
|
141
|
-
assert.equal(added.ok, true);
|
|
142
|
-
const addedContent = toolSearch.output.render({ names: ['read_image'] }, added);
|
|
143
|
-
assertHostToolResultContent(addedContent);
|
|
144
|
-
assert.deepEqual(JSON.parse(addedContent[0].text), added);
|
|
145
|
-
assert.match(addedContent[0].text, /read_image/);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('tool_search error payloads render through the same content-block shape', () => {
|
|
149
|
-
const host = makeHost();
|
|
150
|
-
const toolSearch = host.registration.tools[0];
|
|
151
|
-
const result = toolSearch.invoke({}, { exec: { agent: host.agent } });
|
|
152
|
-
assert.equal(result.error, 'D6_NOT_FROZEN');
|
|
153
|
-
const content = toolSearch.output.render({}, result);
|
|
154
|
-
assertHostToolResultContent(content);
|
|
155
|
-
assert.deepEqual(JSON.parse(content[0].text), result);
|
|
156
|
-
assert.match(content[0].text, /D6_NOT_FROZEN/);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('registerTools forwards tool_search output.render untouched to the host seam', async () => {
|
|
160
|
-
const host = makeHost();
|
|
161
|
-
const captured = [];
|
|
162
|
-
const count = await registerTools(
|
|
163
|
-
{
|
|
164
|
-
tools: {
|
|
165
|
-
register(definition) {
|
|
166
|
-
captured.push(definition);
|
|
167
|
-
return () => {};
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
{ defineTool: (definition) => definition, d6Tools: host.registration.tools },
|
|
172
|
-
);
|
|
173
|
-
assert.equal(count, 23);
|
|
174
|
-
const search = captured.find((definition) => definition.name === 'tool_search');
|
|
175
|
-
assert.ok(search, 'tool_search was not registered through registerTools');
|
|
176
|
-
assert.equal(search.output, TOOL_SEARCH_SPEC.output, 'registerTools must forward the output contract by identity');
|
|
177
|
-
|
|
178
|
-
const value = await search.execute({}, { agent: host.agent });
|
|
179
|
-
assert.equal(value.error, 'D6_NOT_FROZEN');
|
|
180
|
-
const content = search.output.render({}, value);
|
|
181
|
-
assertHostToolResultContent(content);
|
|
182
|
-
assert.deepEqual(JSON.parse(content[0].text), value);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('the rc.2 string shape fails the same host walk (mutation guard)', () => {
|
|
186
|
-
const legacyRender = (_args, value) => JSON.stringify(value);
|
|
187
|
-
const legacyContent = legacyRender({}, { ok: true, added: ['read_image'] });
|
|
188
|
-
assert.equal(typeof legacyContent, 'string');
|
|
189
|
-
assert.equal(Array.isArray(legacyContent), false);
|
|
190
|
-
|
|
191
|
-
// The pre-fix shape must reproduce the reported host failure verbatim.
|
|
192
|
-
assert.throws(() => contentHasImage(legacyContent), /content\.some is not a function/);
|
|
193
|
-
assert.throws(() => assertHostToolResultContent(legacyContent), /content\.some is not a function/);
|
|
194
|
-
});
|