Compare commits
811
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcc0ad4621 | ||
|
|
0b4328d514 | ||
|
|
78be9be83b | ||
|
|
abc0e7092b | ||
|
|
f1b3487a51 | ||
|
|
c649fdae69 | ||
|
|
78167faa4f | ||
|
|
0d03fb3ca3 | ||
|
|
a186602dc3 | ||
|
|
0ddca24030 | ||
|
|
f823eb9c60 | ||
|
|
a9b2873303 | ||
|
|
08afd883d7 | ||
|
|
90ff03beb7 | ||
|
|
c05d36d6f3 | ||
|
|
69db579d7a | ||
|
|
08c061f8a7 | ||
|
|
6fe4c14f1e | ||
|
|
3a51d53966 | ||
|
|
294a359142 | ||
|
|
e9273a7624 | ||
|
|
57aae95097 | ||
|
|
1ffe1a71db | ||
|
|
16743ca088 | ||
|
|
6958fd4e3a | ||
|
|
7053f882a8 | ||
|
|
e1cd73e4de | ||
|
|
001bc22978 | ||
|
|
e85a3f90be | ||
|
|
a2f3a18580 | ||
|
|
6bdaebb3ea | ||
|
|
6447accb5f | ||
|
|
812f846489 | ||
|
|
6d89e7497e | ||
|
|
679f3fbb2c | ||
|
|
4731be7515 | ||
|
|
db109a62fb | ||
|
|
2cee373847 | ||
|
|
e0e2c63a9c | ||
|
|
39471d572d | ||
|
|
d65fd521b5 | ||
|
|
bb3ac8e304 | ||
|
|
14d3a091b8 | ||
|
|
0fdd91f3e6 | ||
|
|
4656d7bf5a | ||
|
|
c5219dcfb2 | ||
|
|
8706daa679 | ||
|
|
4e56134fec | ||
|
|
ee28dc8d1c | ||
|
|
399fe74c1e | ||
|
|
e74ab10d10 | ||
|
|
5a28d81355 | ||
|
|
fa302c8343 | ||
|
|
dedf7e65c5 | ||
|
|
4618fddfb5 | ||
|
|
950b63b37b | ||
|
|
8d8684dc0f | ||
|
|
83e1d6bcc4 | ||
|
|
e2e7264585 | ||
|
|
866b3bd06a | ||
|
|
9445f22471 | ||
|
|
b0279fa8ae | ||
|
|
037a521662 | ||
|
|
36ed2133ff | ||
|
|
dde72c5af1 | ||
|
|
3952e9a08b | ||
|
|
fb69466d62 | ||
|
|
287a77525b | ||
|
|
68312d2a4a | ||
|
|
45bb3da47e | ||
|
|
a0567adccb | ||
|
|
af86de8add | ||
|
|
d70967e36b | ||
|
|
a6f6b98053 | ||
|
|
1d5f9f7607 | ||
|
|
b56220bd3a | ||
|
|
bfc89ed4a9 | ||
|
|
1c9cbf5a7e | ||
|
|
eebc65428b | ||
|
|
b8fd57ad43 | ||
|
|
9ee92455c4 | ||
|
|
ee20b6e9ba | ||
|
|
46983a077f | ||
|
|
799be95642 | ||
|
|
ff42bb3f0d | ||
|
|
985afb0941 | ||
|
|
152d0294c2 | ||
|
|
6ad048912e | ||
|
|
72b3dfdc6f | ||
|
|
cfa6f7359e | ||
|
|
22f0226e49 | ||
|
|
f1923f2e8a | ||
|
|
c72582e587 | ||
|
|
e7065bb5ac | ||
|
|
e369822bc0 | ||
|
|
917aab8204 | ||
|
|
c4ecee9173 | ||
|
|
f6e942a097 | ||
|
|
0e8bfc1b9d | ||
|
|
44ad857798 | ||
|
|
28fcf5116f | ||
|
|
dca8cfb77e | ||
|
|
0f05518e69 | ||
|
|
44df6f18cd | ||
|
|
9cfa6153ca | ||
|
|
43e9de8170 | ||
|
|
4ff169c8ad | ||
|
|
ba3c339cc7 | ||
|
|
882b8b406f | ||
|
|
85da1c0f66 | ||
|
|
c03f27db21 | ||
|
|
fdf8f140ba | ||
|
|
23b678df13 | ||
|
|
15e53db1bc | ||
|
|
9314e2de16 | ||
|
|
e923f8a353 | ||
|
|
b0dd2f044e | ||
|
|
a8eaa9e29e | ||
|
|
6299923878 | ||
|
|
47e4bbe3db | ||
|
|
f9a806c0f1 | ||
|
|
4d7a673e32 | ||
|
|
491251f618 | ||
|
|
013a486fc5 | ||
|
|
7647b11542 | ||
|
|
02e7591591 | ||
|
|
34740e6db0 | ||
|
|
cb6834f011 | ||
|
|
54cb44c006 | ||
|
|
31f783bb90 | ||
|
|
cea324f719 | ||
|
|
ab2bee0051 | ||
|
|
9e761119bb | ||
|
|
681cf973e2 | ||
|
|
13a8cc11c7 | ||
|
|
7c03d2a267 | ||
|
|
41beadfa2a | ||
|
|
6f84e118b1 | ||
|
|
df37050b48 | ||
|
|
fd8dfa270d | ||
|
|
80cec4bde3 | ||
|
|
7b2d7841dd | ||
|
|
a703e9e4b0 | ||
|
|
6c0b6c7aa3 | ||
|
|
3f579d45eb | ||
|
|
2cda0156a8 | ||
|
|
36626c7dfa | ||
|
|
b941eded7c | ||
|
|
d27dc27538 | ||
|
|
0ea2846c3e | ||
|
|
3d1e011ea5 | ||
|
|
00fbab4c14 | ||
|
|
8bd073392e | ||
|
|
447e1b88aa | ||
|
|
797b8b4455 | ||
|
|
8454ed9771 | ||
|
|
8ecdc76b41 | ||
|
|
47d7788fe5 | ||
|
|
24db102f15 | ||
|
|
f590c13c92 | ||
|
|
8577f42832 | ||
|
|
aa035e6845 | ||
|
|
0d18d256c3 | ||
|
|
34d1ff94f7 | ||
|
|
19e5a95802 | ||
|
|
82b953acc5 | ||
|
|
f2c8c243e7 | ||
|
|
85c775322f | ||
|
|
8223beef55 | ||
|
|
bc98ceb69f | ||
|
|
2fb4ea0bec | ||
|
|
c18851aa30 | ||
|
|
e2f5686a13 | ||
|
|
8c523d9be1 | ||
|
|
c9a4e0033e | ||
|
|
ece8f7cb77 | ||
|
|
09f5e52fdd | ||
|
|
385879882c | ||
|
|
61addcbcb9 | ||
|
|
9f57925000 | ||
|
|
fcc1becf11 | ||
|
|
dd9719f67a | ||
|
|
d9d3b80fe3 | ||
|
|
c08621b991 | ||
|
|
0667efe813 | ||
|
|
70d9b57f5c | ||
|
|
df078ddc82 | ||
|
|
a3baa4d6e4 | ||
|
|
0cb47b9d03 | ||
|
|
a4135f1f2f | ||
|
|
3c46127816 | ||
|
|
0753977b60 | ||
|
|
5f1811c91c | ||
|
|
3859913c68 | ||
|
|
7f9d01007f | ||
|
|
48b2f9adc0 | ||
|
|
6a3c52c92b | ||
|
|
912e0383fb | ||
|
|
5b6366f5bf | ||
|
|
cbe275fe99 | ||
|
|
8632896e84 | ||
|
|
d7ca2e0ac2 | ||
|
|
03e654e8a5 | ||
|
|
4f790c874d | ||
|
|
2a7d2f38fb | ||
|
|
b3f301e573 | ||
|
|
06bf7ce024 | ||
|
|
e4ac64c9c7 | ||
|
|
bed550f81e | ||
|
|
21aa215092 | ||
|
|
c3fda8ed51 | ||
|
|
a3f6f94c1e | ||
|
|
cc673ec174 | ||
|
|
e180fce05a | ||
|
|
9ac3ef2ca7 | ||
|
|
1f522605ed | ||
|
|
aa4774e685 | ||
|
|
012a7a2e4a | ||
|
|
f7c184ffd1 | ||
|
|
0535b85a1d | ||
|
|
48e21ac6d7 | ||
|
|
d3e3a2bce9 | ||
|
|
ef5f28d0fb | ||
|
|
eb5b32c9c9 | ||
|
|
c1ab3a24b0 | ||
|
|
582c334472 | ||
|
|
224fe9a658 | ||
|
|
27fb833732 | ||
|
|
87f2531360 | ||
|
|
2ba385e9c9 | ||
|
|
3d47094702 | ||
|
|
9eee00fa7d | ||
|
|
dd8fb95670 | ||
|
|
6e77ea0131 | ||
|
|
9cd3fbec45 | ||
|
|
606729b5b5 | ||
|
|
a4c933ad1f | ||
|
|
63026c7a5c | ||
|
|
52ba88132e | ||
|
|
624c3e91a9 | ||
|
|
f54967a90d | ||
|
|
12cae2a88f | ||
|
|
30bf5733d6 | ||
|
|
4658cba7e0 | ||
|
|
bedcb559ac | ||
|
|
a5c44b9384 | ||
|
|
1f3bc8b22b | ||
|
|
523e741a31 | ||
|
|
9d53909bcf | ||
|
|
be93b94e6e | ||
|
|
be6ed7f0aa | ||
|
|
f8217a4fe1 | ||
|
|
156dad153d | ||
|
|
07f2b259e7 | ||
|
|
0cb15241c0 | ||
|
|
ec7a4e9df5 | ||
|
|
26c9fcfe2e | ||
|
|
6644a234a5 | ||
|
|
d828bf20bc | ||
|
|
5c9029b57e | ||
|
|
7e6ff762e9 | ||
|
|
88b01c24ea | ||
|
|
62c6a2658d | ||
|
|
d85a922e12 | ||
|
|
fdff707b82 | ||
|
|
e0129468c3 | ||
|
|
ec587a015d | ||
|
|
36a0cab333 | ||
|
|
309f438354 | ||
|
|
ca3d19aafb | ||
|
|
401bfa8b3c | ||
|
|
4eb2118dba | ||
|
|
d203f5c418 | ||
|
|
58a3febaf2 | ||
|
|
1d2e1c6be5 | ||
|
|
9ac67a545d | ||
|
|
05225052f9 | ||
|
|
998ebe4e74 | ||
|
|
953500de9f | ||
|
|
fa9f77cc42 | ||
|
|
a7f21a8dd6 | ||
|
|
818e8eced2 | ||
|
|
33566bb43e | ||
|
|
65a87a5860 | ||
|
|
c05a34e153 | ||
|
|
822229c588 | ||
|
|
f387930ebe | ||
|
|
fbfc91bcba | ||
|
|
5a75c8b4a5 | ||
|
|
7301fa98b0 | ||
|
|
ec76a667b1 | ||
|
|
73090b5e56 | ||
|
|
8d0c3b4633 | ||
|
|
ff563d07e0 | ||
|
|
7d9bc01a50 | ||
|
|
4ac71ed747 | ||
|
|
0089109f00 | ||
|
|
2c990f0b14 | ||
|
|
26c077aa5f | ||
|
|
c0795c003a | ||
|
|
a313ff916b | ||
|
|
598006d608 | ||
|
|
f578528f1f | ||
|
|
d2bd3f3893 | ||
|
|
1c9a7e6a71 | ||
|
|
8e30aa7bdc | ||
|
|
77ef574af7 | ||
|
|
a6e406c685 | ||
|
|
dd124c5790 | ||
|
|
56ff20d45f | ||
|
|
683daea572 | ||
|
|
be167ec989 | ||
|
|
2bd4226dc9 | ||
|
|
d052805f96 | ||
|
|
8de3a97f8d | ||
|
|
e17596cf1f | ||
|
|
a2119d6184 | ||
|
|
0039a8879a | ||
|
|
898b2875b3 | ||
|
|
eab19efcf8 | ||
|
|
a7d0d02146 | ||
|
|
e428c74d51 | ||
|
|
24ac78d283 | ||
|
|
0487699ae4 | ||
|
|
36e66f583e | ||
|
|
5b559def7c | ||
|
|
8394c706fc | ||
|
|
93bef27ec7 | ||
|
|
5868dbc357 | ||
|
|
a1c3458e12 | ||
|
|
56e79b50ea | ||
|
|
82095d7f04 | ||
|
|
fe9cd80b28 | ||
|
|
cce6dca987 | ||
|
|
b4c6a40270 | ||
|
|
0071ee8785 | ||
|
|
5a17181af2 | ||
|
|
e02701365c | ||
|
|
f3bf53bfc7 | ||
|
|
c948b16d50 | ||
|
|
45be4c1cb0 | ||
|
|
be1500b4b6 | ||
|
|
934a228d47 | ||
|
|
141a54efed | ||
|
|
908005191d | ||
|
|
8e5fa7bd24 | ||
|
|
ff6960093a | ||
|
|
b6a75e1f54 | ||
|
|
45ae1133bc | ||
|
|
127923cfec | ||
|
|
67644e94ae | ||
|
|
8178dd08c2 | ||
|
|
f5acd9ac51 | ||
|
|
5cf9348314 | ||
|
|
adc54201f3 | ||
|
|
e3c9703eca | ||
|
|
3ff5f9a034 | ||
|
|
16d1566313 | ||
|
|
0744be3d9f | ||
|
|
70bfd4d516 | ||
|
|
8caf3d7838 | ||
|
|
7502558cc5 | ||
|
|
32036b372e | ||
|
|
2653c587ec | ||
|
|
9bc99d7e02 | ||
|
|
68a9a068cd | ||
|
|
1a294f7f79 | ||
|
|
964231856d | ||
|
|
d37b3d98f5 | ||
|
|
b5799c7444 | ||
|
|
ee9ae56599 | ||
|
|
d9e89b3b37 | ||
|
|
61e219d546 | ||
|
|
2499a22e1d | ||
|
|
b71bb26890 | ||
|
|
0392d7c2bd | ||
|
|
9656ecb162 | ||
|
|
5c0bfd1193 | ||
|
|
63ac6b46a6 | ||
|
|
5744837ef5 | ||
|
|
e5a6231573 | ||
|
|
59689da92d | ||
|
|
b177a08e53 | ||
|
|
454374be98 | ||
|
|
43c4919f86 | ||
|
|
71676cbc21 | ||
|
|
dcc3bde068 | ||
|
|
10f10216a3 | ||
|
|
af2c4fedf9 | ||
|
|
b22e90f042 | ||
|
|
d80a818471 | ||
|
|
b90f5fea79 | ||
|
|
6766a694fc | ||
|
|
e8f79f135d | ||
|
|
210e5094d3 | ||
|
|
08b2b984e2 | ||
|
|
b5fed8ca06 | ||
|
|
5a052d6767 | ||
|
|
7123412ae8 | ||
|
|
0eb1bb52e2 | ||
|
|
da10d4de31 | ||
|
|
2361eaebec | ||
|
|
dec3a642df | ||
|
|
f2ad80451d | ||
|
|
850c035fd1 | ||
|
|
6d33eb56ca | ||
|
|
791d04e003 | ||
|
|
42869bf664 | ||
|
|
c766038238 | ||
|
|
51856c3f4f | ||
|
|
8cce23db44 | ||
|
|
999f30aae9 | ||
|
|
1e44387042 | ||
|
|
718f28ee9a | ||
|
|
1b9b05a367 | ||
|
|
64326a0189 | ||
|
|
be0e6fd57a | ||
|
|
4301596be1 | ||
|
|
aa5fde3700 | ||
|
|
99b59d73de | ||
|
|
02e307aef5 | ||
|
|
4d4a7e5859 | ||
|
|
afde188c6f | ||
|
|
7d20d1c361 | ||
|
|
16f54f5ecf | ||
|
|
aaa7f45d4f | ||
|
|
33492246a5 | ||
|
|
9800c764ef | ||
|
|
8504351cc3 | ||
|
|
e5029f419a | ||
|
|
b8ede96fd8 | ||
|
|
2e4fddec5d | ||
|
|
887356ba1a | ||
|
|
159c918680 | ||
|
|
c9d2c5f908 | ||
|
|
fc36b58fc3 | ||
|
|
0c08afe52f | ||
|
|
8f7cef03da | ||
|
|
664e64debe | ||
|
|
63f8e812e1 | ||
|
|
9e0557e2cf | ||
|
|
422cb047a9 | ||
|
|
84b26858d3 | ||
|
|
997fc4b05e | ||
|
|
1287db6c8d | ||
|
|
a0a5a24556 | ||
|
|
1623af7f06 | ||
|
|
5daf251d7c | ||
|
|
42247c31e2 | ||
|
|
3306de499e | ||
|
|
47c6e1da03 | ||
|
|
acf3f66325 | ||
|
|
af37232ab5 | ||
|
|
11c02855e1 | ||
|
|
b95d5c992b | ||
|
|
be7ce3b64b | ||
|
|
80b7985a24 | ||
|
|
a7c8c3ac25 | ||
|
|
cfe6747460 | ||
|
|
4514b99d9c | ||
|
|
b464b633ff | ||
|
|
e87a535d57 | ||
|
|
1c9d95a778 | ||
|
|
a93b1ecdf1 | ||
|
|
f47c61882d | ||
|
|
76a36fee3b | ||
|
|
1644e752ca | ||
|
|
58c375d8a5 | ||
|
|
09bc71f831 | ||
|
|
745c6e7f45 | ||
|
|
e7ce2d2373 | ||
|
|
91474bb32e | ||
|
|
ce988594ee | ||
|
|
3ee7b6829c | ||
|
|
79a33e3211 | ||
|
|
a635d6d908 | ||
|
|
b5197dd07d | ||
|
|
e57fe79696 | ||
|
|
c2cda0bdd7 | ||
|
|
6d0a5f0f12 | ||
|
|
6439400885 | ||
|
|
4247226074 | ||
|
|
a40d1f4c36 | ||
|
|
3b544b0416 | ||
|
|
2b1368f317 | ||
|
|
51177ea96d | ||
|
|
fc1ed8c1cd | ||
|
|
1fc187bf8a | ||
|
|
ef97a03734 | ||
|
|
0036824b70 | ||
|
|
366bd584b4 | ||
|
|
43b7ffa1b3 | ||
|
|
6137225576 | ||
|
|
9a3ad20a15 | ||
|
|
d5ed4eaf97 | ||
|
|
1559737591 | ||
|
|
82ff391f4b | ||
|
|
12dc2f1f58 | ||
|
|
03af1488b4 | ||
|
|
6c05579452 | ||
|
|
970a728e78 | ||
|
|
f5856bd23d | ||
|
|
5b3eadd14e | ||
|
|
f09d260578 | ||
|
|
f248511981 | ||
|
|
b71943900d | ||
|
|
66e9db2b6c | ||
|
|
c400f504e6 | ||
|
|
9743690280 | ||
|
|
dc1eab34cc | ||
|
|
536a4a6c58 | ||
|
|
0cf76b9fad | ||
|
|
ea39687d51 | ||
|
|
c77ecc73f1 | ||
|
|
ba54e7f6ce | ||
|
|
b98549729c | ||
|
|
76cc8b21d1 | ||
|
|
4a5298cc94 | ||
|
|
dea7313318 | ||
|
|
28f71287d9 | ||
|
|
bee33716fe | ||
|
|
8818fdd2e0 | ||
|
|
3be7d56c6d | ||
|
|
066f615cdf | ||
|
|
22db5af493 | ||
|
|
40599db7c8 | ||
|
|
156cd83eb8 | ||
|
|
2e0c73a895 | ||
|
|
04f1a699ee | ||
|
|
53d4ed8f21 | ||
|
|
e86b278053 | ||
|
|
f17a2c24c0 | ||
|
|
207b55a5e6 | ||
|
|
bfe1c5e47c | ||
|
|
1b907b26aa | ||
|
|
4daabfd629 | ||
|
|
4a4aabaa57 | ||
|
|
d8254627fe | ||
|
|
7c0af3101b | ||
|
|
289d6f9f9c | ||
|
|
5acd104bfb | ||
|
|
10c9852f21 | ||
|
|
43a213fdd3 | ||
|
|
9675268c9f | ||
|
|
73f3c6c6f9 | ||
|
|
cf6e05df6d | ||
|
|
38e5684d5c | ||
|
|
a8b1a9b4a8 | ||
|
|
81dc04d87a | ||
|
|
064b1c998c | ||
|
|
953548cd59 | ||
|
|
eab1228daa | ||
|
|
23d968b0ba | ||
|
|
7fad73160d | ||
|
|
fdb514cc29 | ||
|
|
7196f51a63 | ||
|
|
b1b0627c23 | ||
|
|
b690b20ff0 | ||
|
|
5b25f04e6f | ||
|
|
c74015c81e | ||
|
|
15c9e6006b | ||
|
|
106b6f1319 | ||
|
|
7d6342932d | ||
|
|
d62220a4c8 | ||
|
|
acece81128 | ||
|
|
14445c191e | ||
|
|
78083c4ff4 | ||
|
|
122dc3af42 | ||
|
|
8128f8dd2b | ||
|
|
2c59c80608 | ||
|
|
463b19bb90 | ||
|
|
55a8215566 | ||
|
|
a4e6d190d7 | ||
|
|
e752785a65 | ||
|
|
bfcf58b52b | ||
|
|
efe90ca419 | ||
|
|
b2ca187d40 | ||
|
|
9c4dafdfb8 | ||
|
|
80f70969d9 | ||
|
|
6ad043e920 | ||
|
|
aa1540903d | ||
|
|
c49c4f11de | ||
|
|
448f146307 | ||
|
|
1a1ba4f0a9 | ||
|
|
f84c7371ca | ||
|
|
769022766f | ||
|
|
f49f2bcf91 | ||
|
|
15386b4440 | ||
|
|
f09648ddca | ||
|
|
00a2e76b87 | ||
|
|
b117a9a24e | ||
|
|
300e0ded19 | ||
|
|
9c406a6325 | ||
|
|
807657cc05 | ||
|
|
dbcc458c12 | ||
|
|
ffad919ed8 | ||
|
|
5a7a6690a0 | ||
|
|
cbc2738a94 | ||
|
|
a3a235321c | ||
|
|
b5cbd3c08d | ||
|
|
37a570622a | ||
|
|
b3d4933f39 | ||
|
|
c32ac852d3 | ||
|
|
3dde36b5bb | ||
|
|
d7858505da | ||
|
|
849380ee1e | ||
|
|
871a2bab84 | ||
|
|
ad7c75c798 | ||
|
|
e914c285d2 | ||
|
|
c03b1d247e | ||
|
|
976e8b4a2f | ||
|
|
4bec077664 | ||
|
|
e16f966470 | ||
|
|
86d4c75516 | ||
|
|
0b1899534b | ||
|
|
29eb238bed | ||
|
|
44e2be561b | ||
|
|
c9cf2d3116 | ||
|
|
b43a6c9e9c | ||
|
|
c8755f7460 | ||
|
|
27b937944d | ||
|
|
0cab444648 | ||
|
|
6038abe625 | ||
|
|
575606a356 | ||
|
|
f67ca5b866 | ||
|
|
5b1a588284 | ||
|
|
90a6646d6c | ||
|
|
21bd0789ec | ||
|
|
9581e13e78 | ||
|
|
ea009821d6 | ||
|
|
c7a71f753a | ||
|
|
0aee757bf0 | ||
|
|
03ac3ea45e | ||
|
|
16ece34568 | ||
|
|
89e2a525aa | ||
|
|
9d56130730 | ||
|
|
8faf20f660 | ||
|
|
83b04dd138 | ||
|
|
fb59141935 | ||
|
|
55cef1caf7 | ||
|
|
c61f117ba0 | ||
|
|
c9cbdcf739 | ||
|
|
091f00dfc7 | ||
|
|
619c089b5c | ||
|
|
f70979317f | ||
|
|
305e581577 | ||
|
|
13badc4d48 | ||
|
|
a57e5e0d3e | ||
|
|
f3436fe3c2 | ||
|
|
d046335532 | ||
|
|
c6d6c87b3b | ||
|
|
6da2afabf7 | ||
|
|
febeb4e28b | ||
|
|
59edab8c0f | ||
|
|
9ce7b0e6f6 | ||
|
|
cca63c475c | ||
|
|
fb3fb18f48 | ||
|
|
1dfbeb7c3b | ||
|
|
cfd16170f0 | ||
|
|
cd8be93e77 | ||
|
|
4e796f736e | ||
|
|
350c40818a | ||
|
|
ac7165e635 | ||
|
|
a96d8a8bb5 | ||
|
|
3331b91a52 | ||
|
|
eee91744f9 | ||
|
|
e29671eedb | ||
|
|
6a8667f459 | ||
|
|
53aa5f5fd8 | ||
|
|
3cb3d7e5a7 | ||
|
|
0ba1fa7853 | ||
|
|
598e7421e8 | ||
|
|
455a40944e | ||
|
|
8de1fa0cf8 | ||
|
|
d05e9fbd3c | ||
|
|
35018c3726 | ||
|
|
5a30774840 | ||
|
|
a5aa90b6b6 | ||
|
|
a9be73af14 | ||
|
|
eefc765e10 | ||
|
|
e8bf822210 | ||
|
|
eddb6a8862 | ||
|
|
624f862294 | ||
|
|
072c4109e4 | ||
|
|
110e61f70f | ||
|
|
8ad7456367 | ||
|
|
ded397b5d1 | ||
|
|
588cae0f04 | ||
|
|
06d921cc29 | ||
|
|
9aecd40ecb | ||
|
|
bc70995b38 | ||
|
|
8c8b3027a6 | ||
|
|
960705d2b1 | ||
|
|
7f86f79480 | ||
|
|
cafa0b7f54 | ||
|
|
078040e055 | ||
|
|
299eecdc28 | ||
|
|
c89070dc4b | ||
|
|
02a1e013f2 | ||
|
|
82cd8fe6e0 | ||
|
|
47582f4ec9 | ||
|
|
ddc3678cdb | ||
|
|
a6fbd906ba | ||
|
|
55e76567ae | ||
|
|
cc71597e73 | ||
|
|
f664bd98e2 | ||
|
|
aa6a4249b8 | ||
|
|
a6fa50e204 | ||
|
|
191ea92bfa | ||
|
|
990b8831bc | ||
|
|
742133aa38 | ||
|
|
894a825c73 | ||
|
|
7a8e9c8ccf | ||
|
|
fc0f34a618 | ||
|
|
914bfdc381 | ||
|
|
72a492fbb7 | ||
|
|
213eec55f5 | ||
|
|
87f4233cae | ||
|
|
9c58a4b89d | ||
|
|
3b49a0c91d | ||
|
|
05c510575e | ||
|
|
34ed76272c | ||
|
|
fdbe86ef60 | ||
|
|
c65c8a338d | ||
|
|
eefd0e1ef4 | ||
|
|
46144145f4 | ||
|
|
3a575a3385 | ||
|
|
27716078ef | ||
|
|
4e0b05e243 | ||
|
|
1837928d04 | ||
|
|
8603c173e1 | ||
|
|
049e1d3d76 | ||
|
|
6a8425ac38 | ||
|
|
72aeefb633 | ||
|
|
3c0bbdfeb1 | ||
|
|
c3a4c95800 | ||
|
|
d00bcae24d | ||
|
|
ba0da1c4d6 | ||
|
|
4ff75d1586 | ||
|
|
fcb837620a | ||
|
|
d26e9bef67 | ||
|
|
010f584c13 | ||
|
|
709b144017 | ||
|
|
ce50737b41 | ||
|
|
dcfe7479ac | ||
|
|
f2bd43f4e8 | ||
|
|
34f94a974e | ||
|
|
2f9dec8fbd | ||
|
|
3f67f2ce2b | ||
|
|
d5ef28def1 | ||
|
|
59588a8cc8 | ||
|
|
7fd0dba80d | ||
|
|
0518302384 | ||
|
|
296eb6af45 | ||
|
|
dccc3ecf6f | ||
|
|
3cd0624b8d | ||
|
|
a5cf681cc9 | ||
|
|
a9ddb4271e | ||
|
|
7ec9cc2c6d | ||
|
|
9ed7f57c75 | ||
|
|
11479fca13 | ||
|
|
1e477804c6 | ||
|
|
4b6fd16be3 | ||
|
|
71d5e61aa1 | ||
|
|
032cd273a3 | ||
|
|
6050bab6cc | ||
|
|
fe03b5f070 | ||
|
|
802da1f916 | ||
|
|
6719b28294 | ||
|
|
072fab7181 | ||
|
|
3f1770b98e | ||
|
|
f7ef25e89b | ||
|
|
94e09fab6d | ||
|
|
c12b80daaf | ||
|
|
d56c84264c | ||
|
|
5ea147c87e | ||
|
|
29d6afdeb9 | ||
|
|
7d3083f1e5 | ||
|
|
5db30c26f2 | ||
|
|
9b64765045 | ||
|
|
27ae5a9022 | ||
|
|
eb0a43c796 | ||
|
|
c98330ab04 | ||
|
|
471b1767d2 | ||
|
|
94e2fd84fc | ||
|
|
fac0bd08e8 | ||
|
|
4a9ec1ac81 | ||
|
|
3cbe0fbebf | ||
|
|
015bf9c04d | ||
|
|
eaefd91ac7 | ||
|
|
080ce25425 | ||
|
|
5428df154f | ||
|
|
8a3d86c683 | ||
|
|
efbb6d5e1c | ||
|
|
ff94a57778 | ||
|
|
f9c5dd237b | ||
|
|
08e8815e85 | ||
|
|
bc486922d8 | ||
|
|
64e9caa667 | ||
|
|
5c928aa40e | ||
|
|
6a1c50825f | ||
|
|
35b18c04bf | ||
|
|
48857665d1 | ||
|
|
5285c6dcc4 | ||
|
|
09570cf383 | ||
|
|
bf1e5d470e | ||
|
|
032fd5f6d5 | ||
|
|
2425fa4f84 | ||
|
|
78d7690b79 | ||
|
|
6acca42d71 | ||
|
|
0648ca528f |
No files matched your search
@@ -2662,6 +2662,9 @@
|
||||
"simplification/2026-09-19-deepseek-messages-only.i18n.yaml": "sha256:7ffd708c5823c39cc9536178292e7e7fa57db5267bbc9acdda0a87e4953d5ba8",
|
||||
"simplification/2026-09-19-deepseek-messages-only.md": "sha256:241861200ce73e02f77d182633d6ea10b1f29c696c81bff002680762db01d9b5",
|
||||
"simplification/2026-09-19-deepseek-messages-only.zh.md": "sha256:311ef6f8f4cf60dae2a43ca94d772028b645631501fb3648aaeb406ba174991e",
|
||||
"simplification/2026-09-26-team-direct-inbox.i18n.yaml": "sha256:9a6f1137f0c68d976fda4f44c5c301b851a8ee8e46965f3ea808ec5551045fb0",
|
||||
"simplification/2026-09-26-team-direct-inbox.md": "sha256:88f7d9be3ae0b740ddb5a61a16e3c64044a207584b973acab8c3a40c36a7ff30",
|
||||
"simplification/2026-09-26-team-direct-inbox.zh.md": "sha256:d6d1fafa4aa7035a11930cca3bf998d676a62b5f9b75176dff69c3c5ff6b92f0",
|
||||
"testing/2026-06-11-property-based-testing.i18n.yaml": "sha256:9d378912101ad30203f1fcbcad23e0f5d6ec6f39e04a83f17f90887b5c5d761e",
|
||||
"testing/2026-06-11-property-based-testing.md": "sha256:cca3b6299784cff94965f21b6d59966109a0bb41fdc2f8cf56653fa6c417a20a",
|
||||
"testing/2026-06-11-property-based-testing.zh.md": "sha256:09f3d1e2defd2792f87fb0e9892fb7e1db006f3c75ea4581f97e9cde4ea98175",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/archived/simplification/2026-09-26-team-direct-inbox.md
|
||||
2026-09-26-team-direct-inbox.md: 9b90d7ae30e0b711c602de9280cc11845d0525f0
|
||||
2026-09-26-team-direct-inbox.zh.md: 8838fb4a2824838b76142f30a6ff4d0aedf0276b
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: Team messages use the target inbox
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-10-15
|
||||
|
||||
English | [中文](2026-09-26-team-direct-inbox.zh.md)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: Team 消息使用目标 inbox
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-10-15
|
||||
|
||||
[English](2026-09-26-team-direct-inbox.md) | 中文
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
en: ca120635fc40431c
|
||||
zh: 6be7c5ae3c86cdcc
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/problem:
|
||||
en: c8b932b02a4e1c80
|
||||
zh: 96c466be1a521f3f
|
||||
en: df1e82a74c42fa1d
|
||||
zh: 8d5452360a0dec8a
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/decision:
|
||||
en: 2e08d801099cbe18
|
||||
zh: 8c5ab162ada9cc41
|
||||
en: 7a4921d82cd11749
|
||||
zh: 6ab506109afc1351
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/package-topology:
|
||||
en: 5ea3ddfae82df181
|
||||
zh: 2d05d95e51081696
|
||||
en: 3394139c17d28ba8
|
||||
zh: 66857733ba51d98c
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/ctx-web-contract:
|
||||
en: b432f80a424c3e49
|
||||
zh: faa26925fdd98387
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/provider-availability-and-selection:
|
||||
en: 4e4ec068c2a22383
|
||||
zh: b7030d0f00bfdb74
|
||||
en: afc540d986e00d68
|
||||
zh: 84795770d00228c3
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/search-request-and-result-schema:
|
||||
en: ec3703b7e7c6b23d
|
||||
zh: 325755e6e90092da
|
||||
en: ccf6404ae7f3172e
|
||||
zh: 3253035ea49e2548
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/fetch-request-and-result-schema:
|
||||
en: 6dba3b9a51c28b6d
|
||||
zh: 58a93f6ba2d269ea
|
||||
@@ -33,8 +33,8 @@
|
||||
en: 3440f2b34b93f4a1
|
||||
zh: 57a8f784c7e26eff
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/testing:
|
||||
en: 7f73699ac4459432
|
||||
zh: c16be49ac50c34c8
|
||||
en: 03da2f3422183384
|
||||
zh: 10fede51a415ce11
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/alternatives-considered:
|
||||
en: 6119e2aff3b6be5a
|
||||
zh: 76136ef5249c23eb
|
||||
@@ -42,8 +42,8 @@
|
||||
en: e0d84a9b1b9c2123
|
||||
zh: 9e116a7b96751563
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/alternatives-considered/put-provider-dispatch-directly-in-dsh-tool-web:
|
||||
en: dc01d014d03b49d2
|
||||
zh: b956b02f41c62682
|
||||
en: 68ef45e8e5a36de9
|
||||
zh: 0013786ed77e03a8
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/alternatives-considered/split-search-and-fetch-into-two-seams-dsh-search-dsh-fetch:
|
||||
en: 3fb72ba3e554f20a
|
||||
zh: aff5f0add5773388
|
||||
@@ -63,11 +63,11 @@
|
||||
en: 3ea94ffb142d144a
|
||||
zh: 89645f68412f6769
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/consequences:
|
||||
en: 9774448fd6b21c41
|
||||
zh: 8411c590ad70d5c4
|
||||
en: 8e7523b2858efaf3
|
||||
zh: aff8fc74eb439ed6
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/deferred-work:
|
||||
en: 48b7b8e7310cd56b
|
||||
zh: d406cd6a8a649e58
|
||||
en: ddb599fd2ac1647c
|
||||
zh: c0b8f446cf483b3c
|
||||
/agent-note-web-capability-seam-stable-tools-over-multiple-providers/open-questions:
|
||||
en: 63848c6ef9a6ab44
|
||||
zh: 041b340802091e60
|
||||
@@ -6,7 +6,7 @@ English | [中文](2026-06-24-web-capability-seam.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The harness needs model-facing web tools without binding the model contract to one vendor's API shape. Search is the immediate pressure point: supporting both Exa search and Perplexity search from the start — two deliberately different provider shapes (Exa returns a flat `results[]` of `{title, url, highlights, publishedDate}`; Perplexity returns a generated answer plus citations) — is what proves the normalized web contract does not just mirror one vendor. Fetch is a separate operation: an anonymous public HTTP(S) fetch backend has transport, security, redirect, decoding, and size-limit concerns that are not the same as provider-backed search.
|
||||
The harness needs model-facing web tools without binding the model contract to one vendor's API shape. Search is the immediate pressure point: search APIs return deliberately different formats (Exa returns a flat `results[]` of `{title, url, highlights, publishedDate}`, Perplexity returns a generated answer plus citations, and the DeepSeek Search API returns a flat `results[]` with snippets and summaries), and the normalized web contract must accept each of them without mirroring one vendor. The seam was designed against the Exa and Perplexity formats; neither provider ships, and a plugin can register one. Fetch is a separate operation: an anonymous public HTTP(S) fetch backend has transport, security, redirect, decoding, and size-limit concerns that are not the same as provider-backed search.
|
||||
|
||||
The model-facing API must stay stable while backends change. A search provider swap should not change how the model asks for a query, and a fetch implementation swap should not change how the model asks for a URL. Conversely, a provider package should not expose its own model-facing tool schema just because it has extra provider-specific knobs.
|
||||
|
||||
@@ -19,7 +19,7 @@ There is also a provider-selection question. Existing `tool-bash` and `tool-fs`
|
||||
Web access is a first-class capability seam following [the capability-seam Agent Note](2026-06-13-capability-seams.md):
|
||||
|
||||
1. `@deepseek-ai/dsh-web` (`packages/web/web`) owns `ctx.web`, provider registration, provider selection, shared request/result vocabulary, and web-specific errors.
|
||||
2. Provider packages implement concrete backends and register capabilities with `ctx.web`, for example `@deepseek-ai/dsh-web-search-exa`, `@deepseek-ai/dsh-web-search-perplexity`, `@deepseek-ai/dsh-web-search-deepseek`, and `@deepseek-ai/dsh-web-fetch-http`.
|
||||
2. Provider packages implement concrete backends and register capabilities with `ctx.web`, for example `@deepseek-ai/dsh-web-search-deepseek`, `@deepseek-ai/dsh-web-deepseek-api`, and `@deepseek-ai/dsh-web-fetch-http`.
|
||||
3. `@deepseek-ai/dsh-tool-web` (`packages/web/tool-web`) owns the model-facing `web_search` and `web_fetch` tool schemas, prompt sections, argument validation, result formatting, and tool-owned presentation over `ctx.web`.
|
||||
|
||||
Providers do not register tools. Providers register capabilities. `dsh-tool-web` is the only owner of model-facing names, descriptions, prompt guidance, JSON schemas, and presentation.
|
||||
@@ -32,9 +32,9 @@ Search and fetch are separate tools but one web-access seam. `ctx.web` owns prov
|
||||
- A tool is never unregistered merely because its selected provider is missing, misconfigured, missing credentials, ambiguous, or temporarily unavailable.
|
||||
- The provider is resolved at execution time, and a structured `WebError` is returned when the selected capability cannot run.
|
||||
|
||||
This keeps the model schema stable without making plugin load order, credential state, or HMR timing part of the model-facing contract. If web search is enabled but no usable search provider exists, `web_search` remains visible and execution fails with a structured `WebError` such as `WEB_PROVIDER_UNAVAILABLE` or `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. `dsh-tool-web` narrows this per model request: it [withholds `web_search` while the selected provider reports that the request's search cannot authenticate](2026-09-27-web-search-offered-by-provider-readiness.md). If a provider appears after `dsh-tool-web`, the next execution can use it without changing the schema. If a provider disappears mid-call, execution fails with a structured `WebError` instead of silently choosing another provider or falling through to `UNKNOWN_TOOL`.
|
||||
This keeps the model schema stable without making plugin load order, credential state, or HMR timing part of the model-facing contract. If web search is enabled but no usable search provider exists, `web_search` remains visible and execution fails with a structured `WebError` such as `WEB_PROVIDER_UNAVAILABLE` or `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. `dsh-tool-web` narrows this per model request: it [withholds `web_search` or `web_fetch` while the selected provider reports that the request's operation cannot authenticate](2026-09-27-web-search-offered-by-provider-readiness.md). If a provider appears after `dsh-tool-web`, the next execution can use it without changing the schema. If a provider disappears mid-call, execution fails with a structured `WebError` instead of silently choosing another provider or falling through to `UNKNOWN_TOOL`.
|
||||
|
||||
The seam deliberately exposes no observation surface — no registry-change event and no aggregated capability-status query. Unavailability is a fact a caller observes by executing: `search()`/`fetch()` resolve the provider at call time and throw the structured `WebError` that names what failed. [The observation-surface Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) records that judgment: derived-on-call selection and enablement-based registration leave no consumer that needs a change signal or an availability probe distinct from executing and routing the error, and a future provider-status panel reintroduces the smallest signal or query it actually consumes.
|
||||
The seam deliberately exposes no availability observation — no aggregated capability-status query and no per-provider availability report. Unavailability is a fact a caller observes by executing: `search()`/`fetch()` resolve the provider at call time and throw the structured `WebError` that names what failed. [The observation-surface Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) records that judgment: derived-on-call selection and enablement-based registration leave no consumer that needs a change signal or an availability probe distinct from executing and routing the error, and a future provider-status panel reintroduces the smallest signal or query it actually consumes. Provider pickers consume registration, not availability: `listProviders()` and the Remote method `web/listProviders` list every registered provider with its optional `title` and `description` (including unavailable providers), and the payload-free `web/providers-changed` event fires after each registry mutation. The [web provider selection decision](2026-10-29-web-provider-selection.md) records why registration is observable while availability is not.
|
||||
|
||||
## Package topology
|
||||
|
||||
@@ -43,11 +43,9 @@ The three-package Service Definition / Service Provider / Consumer split follows
|
||||
The dependency direction mirrors bash and filesystem:
|
||||
|
||||
```text
|
||||
@deepseek-ai/dsh-tool-web --depends on--> @deepseek-ai/dsh-web <--depends on-- @deepseek-ai/dsh-web-search-exa
|
||||
@deepseek-ai/dsh-tool-web --depends on--> @deepseek-ai/dsh-web <--depends on-- @deepseek-ai/dsh-web-search-deepseek
|
||||
consumer interface implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-search-perplexity
|
||||
implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-search-deepseek
|
||||
<--depends on-- @deepseek-ai/dsh-web-deepseek-api
|
||||
implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-fetch-http
|
||||
implementation
|
||||
@@ -57,9 +55,7 @@ At runtime, provider packages register capabilities with `ctx.web`; `tool-web` r
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
exa["@deepseek-ai/dsh-web-search-exa"] -->|registerSearchProvider| web["@deepseek-ai/dsh-web / ctx.web"]
|
||||
perplexity["@deepseek-ai/dsh-web-search-perplexity"] -->|registerSearchProvider| web
|
||||
deepseek["@deepseek-ai/dsh-web-search-deepseek"] -->|registerSearchProvider| web
|
||||
deepseek["@deepseek-ai/dsh-web-search-deepseek"] -->|registerSearchProvider| web["@deepseek-ai/dsh-web / ctx.web"]
|
||||
deepseekApi["@deepseek-ai/dsh-web-deepseek-api"] -->|registerSearchProvider / registerFetchProvider| web
|
||||
fetchLocal["@deepseek-ai/dsh-web-fetch-http"] -->|registerFetchProvider| web
|
||||
toolWeb["@deepseek-ai/dsh-tool-web"] -->|search/fetch| web
|
||||
@@ -67,9 +63,9 @@ flowchart LR
|
||||
toolWeb -->|ctx.tools.register| webFetch["tool: web_fetch"]
|
||||
```
|
||||
|
||||
`@deepseek-ai/dsh-web` depends only on Cordis and low-level harness support. It declares `ctx.web`, provider interfaces, request/result types, the provider availability contract, and error codes. It does not import tool, agent, session, LLM, or provider packages.
|
||||
`@deepseek-ai/dsh-web` depends only on Cordis and low-level harness support, including the Typert protocol for its Remote method. It declares `ctx.web`, provider interfaces, request/result types, the provider availability contract, and error codes. It does not import tool, agent, session, LLM, or provider packages.
|
||||
|
||||
Provider packages depend only on `dsh-web` and Cordis. They own credentials, endpoints, wire mapping, parsing, and `WebError` translation, using platform `fetch`. Each provider injects the shared service and registers a backend; only `dsh-web` owns the `ctx.web` key. Provider-private protocol shapes do not create dependencies on `ctx.llm` or a Cordis HTTP service.
|
||||
Provider packages depend on `dsh-web`, Cordis, and their credential/configuration helpers. They own credentials, endpoints, wire mapping, parsing, and `WebError` translation, using platform `fetch`. Each provider injects the shared service and registers a backend; only `dsh-web` owns the `ctx.web` key. Provider-private protocol shapes do not create dependencies on `ctx.llm` or a Cordis HTTP service.
|
||||
|
||||
`@deepseek-ai/dsh-tool-web` depends on `@deepseek-ai/dsh-web`, `@deepseek-ai/dsh-tools`, `@deepseek-ai/dsh-system-prompt`, and Cordis. It never imports concrete provider packages.
|
||||
|
||||
@@ -78,16 +74,22 @@ Provider packages depend only on `dsh-web` and Cordis. They own credentials, end
|
||||
`ctx.web` is a provider registry plus a provider-selecting execution API. The registry half stays close to `LlmRuntime`: a `Map<id, provider>` per capability kind, `registerSearchProvider` / `registerFetchProvider` methods that return disposers, duplicate ids that throw `WebError`, and execution-time resolution that throws when the selected provider is absent or unusable. The authoritative signatures live in `packages/web/web/src/types.ts`; the seam's shape:
|
||||
|
||||
```ts
|
||||
import type { WebFetchRequest, WebFetchResult, WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web'
|
||||
import type {
|
||||
WebFetchRequest, WebFetchResult, WebProviderInfo, WebProviderList, WebSearchRequest, WebSearchResult,
|
||||
} from '@deepseek-ai/dsh-web'
|
||||
|
||||
interface WebSearchProvider {
|
||||
readonly id: string
|
||||
readonly title?: string
|
||||
readonly description?: WebProviderInfo['description']
|
||||
available(): boolean
|
||||
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
|
||||
}
|
||||
|
||||
interface WebFetchProvider {
|
||||
readonly id: string
|
||||
readonly title?: string
|
||||
readonly description?: WebProviderInfo['description']
|
||||
available(): boolean
|
||||
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
|
||||
}
|
||||
@@ -95,6 +97,7 @@ interface WebFetchProvider {
|
||||
interface WebRuntime {
|
||||
registerSearchProvider(provider: WebSearchProvider): () => void
|
||||
registerFetchProvider(provider: WebFetchProvider): () => void
|
||||
listProviders(): WebProviderList
|
||||
|
||||
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
|
||||
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
|
||||
@@ -131,18 +134,15 @@ The "single provider auto-selects" rule is for tests, demos, and simple deployme
|
||||
- id: web
|
||||
name: '@deepseek-ai/dsh-web'
|
||||
config:
|
||||
searchProvider: exa
|
||||
searchProvider: deepseek-search-api
|
||||
fetchProvider: http
|
||||
|
||||
- id: web-search-exa
|
||||
name: '@deepseek-ai/dsh-web-search-exa'
|
||||
|
||||
- id: web-search-perplexity
|
||||
name: '@deepseek-ai/dsh-web-search-perplexity'
|
||||
|
||||
- id: web-search-deepseek
|
||||
name: '@deepseek-ai/dsh-web-search-deepseek'
|
||||
|
||||
- id: web-deepseek-api
|
||||
name: '@deepseek-ai/dsh-web-deepseek-api'
|
||||
|
||||
- id: web-fetch-http
|
||||
name: '@deepseek-ai/dsh-web-fetch-http'
|
||||
|
||||
@@ -150,9 +150,9 @@ The "single provider auto-selects" rule is for tests, demos, and simple deployme
|
||||
name: '@deepseek-ai/dsh-tool-web'
|
||||
```
|
||||
|
||||
Operational overrides feed the same explicit selection path: `DSH_WEB_SEARCH_PROVIDER=perplexity` is equivalent to config `searchProvider: perplexity`, not a hidden priority chain inside `dsh-tool-web`.
|
||||
Operational overrides feed the same explicit selection path: `DSH_WEB_SEARCH_PROVIDER=deepseek-search-api` is equivalent to config `searchProvider: deepseek-search-api`, not a hidden priority chain inside `dsh-tool-web`.
|
||||
|
||||
Each search or fetch reads the configured id when it runs, so a saved settings change selects another provider for the next operation without remounting `dsh-web`. The Web search settings switch relies on this to move both capabilities to the [DeepSeek Search and Fetch APIs](../feature/2026-10-15-deepseek-search-fetch-api.md) at once.
|
||||
Each search or fetch reads the configured id when it runs, so a saved settings change selects another provider for the next operation without remounting `dsh-web`. The Search and Fetch menus on the Web search and fetch settings page rely on this to switch each capability's provider independently, including the [DeepSeek Search and Fetch APIs](../feature/2026-10-15-deepseek-search-fetch-api.md); the [web provider selection decision](2026-10-29-web-provider-selection.md) owns the shipped defaults.
|
||||
|
||||
`ctx.web.search()` and `ctx.web.fetch()` resolve the provider at execution time using the selection rules above. If the selected capability is unavailable, they throw `WebError` with a structured code such as `WEB_PROVIDER_UNAVAILABLE`, `WEB_PROVIDER_CONFIGURED_MISSING`, `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`, or `WEB_PROVIDER_AMBIGUOUS`. If no provider is explicitly configured and no usable provider exists, the execution error is the generic `WEB_PROVIDER_UNAVAILABLE` case; there is deliberately no diagnostic summary of every unavailable provider.
|
||||
|
||||
@@ -168,8 +168,8 @@ The `web_search` model-facing tool is small. The only model-facing argument is:
|
||||
|
||||
- `dsh-tool-web` owns the value and puts it on `WebSearchRequest.maxResults`.
|
||||
- `ctx.web` passes the request through to the selected provider unchanged.
|
||||
- A provider applies `maxResults` at the request layer when its API supports it (Exa's `numResults`), as a cost/latency optimization.
|
||||
- `ctx.web` enforces the bound on the result: if a provider returns more than `maxResults` sources — because its API has no result-count control (Perplexity) or ignored the hint — the seam truncates `sources[]` to `maxResults` and sets `WebSearchResult.truncated` to `true` before returning. This makes the bound a single cross-provider guarantee the model-facing layer can rely on, rather than something each provider must remember to honor.
|
||||
- A provider applies `maxResults` at the request layer when its API supports it (the DeepSeek Search API's `count`), as a cost/latency optimization.
|
||||
- `ctx.web` enforces the bound on the result: if a provider returns more than `maxResults` sources — because its API has no result-count control or ignored the hint — the seam truncates `sources[]` to `maxResults` and sets `WebSearchResult.truncated` to `true` before returning. This makes the bound a single cross-provider guarantee the model-facing layer can rely on, rather than something each provider must remember to honor.
|
||||
|
||||
The seam request carries no provider-specific controls — no Perplexity model selection, search recency, domain filters, Exa `livecrawl`, Exa `type`, regional hints, generated-answer budgets, or search depth. Such a field is added only when it has provider-neutral semantics that both the tool schema and selected providers can honor honestly.
|
||||
|
||||
@@ -190,13 +190,14 @@ interface WebSearchSource {
|
||||
readonly url: string
|
||||
readonly title?: string
|
||||
readonly snippet?: string
|
||||
readonly summary?: string
|
||||
readonly publishedAt?: string
|
||||
}
|
||||
```
|
||||
|
||||
`content` is optional provider-generated answer text, search context, or summary. `sources[]` is the portable citation shape. A source always has a URL; title, snippet, and `publishedAt` are optional because not every provider returns them. `title` is not required: Perplexity-style citations may provide only URLs, and forcing adapters to invent titles would make the seam lie. `dsh-tool-web` renders a `title ?? hostname(url)`-style fallback label for display. `publishedAt` is an optional publication/crawl timestamp as an ISO-8601 string — Exa returns it as `publishedDate` on each result and Perplexity returns a `date` on search results, so it is real provider data, not derived; the seam carries it as a string and leaves date parsing to the consumer.
|
||||
`content` is optional provider-generated answer text, search context, or summary. `sources[]` is the portable citation shape. A source always has a URL; title, snippet, summary, and `publishedAt` are optional because not every provider returns them. `snippet` is a short excerpt for display, and `summary` is a longer excerpt for model reading whose length the consumer bounds. `title` is not required: Perplexity-style citations may provide only URLs, and forcing adapters to invent titles would make the seam lie. `dsh-tool-web` renders a `title ?? hostname(url)`-style fallback label for display. `publishedAt` is an optional publication/crawl timestamp as an ISO-8601 string — the DeepSeek Search API returns it as a nullable `date` on each result, Exa as `publishedDate`, and Perplexity as `date`, so it is real provider data, not derived; the seam carries it as a string and leaves date parsing to the consumer.
|
||||
|
||||
Exa search maps each entry of the provider's flat `results[]` into a `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← the first `highlights[]` entry (an entry with no highlight has no portable snippet and is dropped), `publishedAt` ← `publishedDate`. Exa returns no provider-generated answer, so `content` is omitted. Perplexity search maps `choices[0].message.content` to `content` and prefers the structured top-level `search_results[]` for `sources[]` — `url` ← `url`, `title` ← `title`, `snippet` ← `snippet` (often empty), `publishedAt` ← `date` — falling back to the URL-only `citations[]` array only when `search_results` is absent (those sources carry just a `url`). If a provider returns fewer structured fields than the seam supports, the adapter omits those optional fields.
|
||||
The DeepSeek Search API provider maps each entry of the response's `results[]` into a `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← `snippet`, `summary` ← `summary`, `publishedAt` ← `date`, omitting empty values. It returns no provider-generated answer, so `content` is omitted. If a provider returns fewer structured fields than the seam supports, the adapter omits those optional fields.
|
||||
|
||||
Full page retrieval remains the job of `web_fetch(url)`. Search snippets are discovery context, not fetched page bodies.
|
||||
|
||||
@@ -283,7 +284,7 @@ Tool execution lets these errors flow through `ToolRuntime.execute()`, which alr
|
||||
|
||||
## Testing
|
||||
|
||||
Each layer is pinned at its own boundary: the registry/selection/truncation/abort contract and the `WebError` codes in `dsh-web`; per-provider request/response mapping over recorded fixtures (Perplexity fixtures include URL-only citations so the optional source fields stay honest) plus a self-skipping with-key smoke per real provider; real local-HTTP behavior in `web-fetch-http`; and enablement-driven registration, structured execution errors, and result formatting through the real tool registry in `dsh-tool-web`. A real-Loader smoke guards the two export shapes ([postmortem 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md)): `dsh-web` is a default-exported service, while the providers and `tool-web` are namespace plugins where a stray `export default` would drop `inject`.
|
||||
Each layer is pinned at its own boundary: the registry/selection/truncation/abort contract and the `WebError` codes in `dsh-web`; per-provider request/response mapping over recorded fixtures plus a self-skipping with-key smoke per real provider; real local-HTTP behavior in `web-fetch-http`; and enablement-driven registration, structured execution errors, and result formatting through the real tool registry in `dsh-tool-web`. A real-Loader smoke guards the two export shapes ([postmortem 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md)): `dsh-web` is a default-exported service, while the providers and `tool-web` are namespace plugins where a stray `export default` would drop `inject`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -293,7 +294,7 @@ This matches the most flexible provider-plugin systems: every provider can expos
|
||||
|
||||
### Put provider dispatch directly in `dsh-tool-web`
|
||||
|
||||
This resembles OpenCode's local web search: one stable `websearch` tool dispatches to Exa or Parallel internally. It is acceptable for a small product path but wrong as a harness foundation. The tool package would own provider selection, credentials, request mapping, transport, response parsing, and presentation, making it hard to add Exa and Perplexity without baking their differences into the tool schema.
|
||||
This resembles OpenCode's local web search: one stable `websearch` tool dispatches to Exa or Parallel internally. It is acceptable for a small product path but wrong as a harness foundation. The tool package would own provider selection, credentials, request mapping, transport, response parsing, and presentation, making it hard to add providers with different formats without baking their differences into the tool schema.
|
||||
|
||||
### Split search and fetch into two seams (`dsh-search`, `dsh-fetch`)
|
||||
|
||||
@@ -321,9 +322,9 @@ Rejected for the shipped presets. Per-call confirmation would interrupt ordinary
|
||||
|
||||
## Consequences
|
||||
|
||||
**The search schema is deliberately thin.** Exa and Perplexity both expose useful provider-specific controls; a control is added only once it can be defined provider-neutrally and enforced honestly by both tool registration and provider execution.
|
||||
**The search schema is deliberately thin.** Search APIs expose useful provider-specific controls, such as Exa's `livecrawl`, Perplexity's search recency, and the DeepSeek Search API's `localization`; a control is added only once it can be defined provider-neutrally and enforced honestly by both tool registration and provider execution.
|
||||
|
||||
**Perplexity citations can be sparse.** A citation may be only a URL. Making `title` and `snippet` optional keeps the seam truthful but means `tool-web` renders fallback labels.
|
||||
**Citations can be sparse.** A Perplexity-style citation may be only a URL. Making `title` and `snippet` optional keeps the seam truthful but means `tool-web` renders fallback labels.
|
||||
|
||||
**Stable tool registration defers misconfiguration to execution.** Keeping the tool visible is correct when the product enabled web access, but product apps that expect web search should surface the structured `WEB_PROVIDER_CONFIGURED_MISSING` / `WEB_PROVIDER_CONFIGURED_UNAVAILABLE` / `WEB_PROVIDER_AMBIGUOUS` failures loudly so users do not discover setup problems only after the model calls the tool.
|
||||
|
||||
@@ -337,7 +338,7 @@ Rejected for the shipped presets. Per-call confirmation would interrupt ordinary
|
||||
|
||||
- A `pdf` `WebFetchBody` kind: the `http` provider decodes text-extractable PDFs (best-effort, capped, `truncated`) into a `{ kind: 'pdf'; content; pageCount? }` arm, and `tool-web` renders it. This is fetch, not `web_extract` — PDF retrieval is a concrete HTTP 200 plus deterministic local decoding, not provider-side extraction of a non-HTTP resource. Adding it is a coordinated change across `dsh-web` (declare the arm), the provider (decode + narrow "binary rejection" to "reject binary except text-extractable PDF"; scanned/image PDFs needing OCR stay out of scope), and `tool-web` (render). The closed `WebFetchBody` union makes the consumer side fail to compile until the new arm is handled.
|
||||
- Provider-backed extraction as a separate `web_extract` capability, rather than widening `web_fetch` silently.
|
||||
- Provider-neutral search controls beyond `query` and `maxResults`, once Exa and Perplexity can both honor them honestly.
|
||||
- Provider-neutral search controls beyond `query` and `maxResults`, once the registered providers can honor them honestly.
|
||||
|
||||
## Open questions
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
harness 需要面向模型的 web 工具,但不能将模型约定绑定到某一家厂商的 API 形状上。搜索是当前的压力点:从一开始就同时支持 Exa 搜索和 Perplexity 搜索——两种刻意不同的提供方形状(Exa 返回扁平的 `results[]`,每项包含 `{title, url, highlights, publishedDate}`;Perplexity 返回一段生成式回答加引用列表)——正是用来证明归一化的 web 约定并非只是镜像某一家厂商。Fetch 是另一项独立操作:匿名公开 HTTP(S) fetch 后端涉及传输、安全、重定向、解码和大小限制等关注点,与提供方支撑的搜索并不相同。
|
||||
harness 需要面向模型的 web 工具,但不能将模型约定绑定到某一家厂商的 API 形状上。搜索是当前的压力点:各搜索 API 返回刻意不同的格式(Exa 返回扁平的 `results[]`,每项包含 `{title, url, highlights, publishedDate}`;Perplexity 返回一段生成式回答加引用列表;DeepSeek Search API 返回带 snippet 与摘要的扁平 `results[]`),归一化的 web 约定必须能接纳每一种格式,而不是镜像某一家厂商。seam 是以 Exa 和 Perplexity 的格式为参照设计的;这两个提供方都不随附,插件可以注册它们。Fetch 是另一项独立操作:匿名公开 HTTP(S) fetch 后端涉及传输、安全、重定向、解码和大小限制等关注点,与提供方支撑的搜索并不相同。
|
||||
|
||||
面向模型的 API 必须保持稳定,而后端可以更换。更换搜索提供方不应改变模型发起查询的方式;更换 fetch 实现不应改变模型请求 URL 的方式。反过来,提供方包也不应仅仅因为自己有额外的提供方特有旋钮就暴露自己的面向模型工具 schema。
|
||||
|
||||
@@ -19,7 +19,7 @@ harness 需要面向模型的 web 工具,但不能将模型约定绑定到某
|
||||
Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13-capability-seams.zh.md):
|
||||
|
||||
1. `@deepseek-ai/dsh-web`(`packages/web/web`)拥有 `ctx.web`、提供方注册、提供方选择、共享的请求/结果词汇,以及 web 特有的错误。
|
||||
2. 提供方包实现具体后端并向 `ctx.web` 注册能力,例如 `@deepseek-ai/dsh-web-search-exa`、`@deepseek-ai/dsh-web-search-perplexity`、`@deepseek-ai/dsh-web-search-deepseek` 和 `@deepseek-ai/dsh-web-fetch-http`。
|
||||
2. 提供方包实现具体后端并向 `ctx.web` 注册能力,例如 `@deepseek-ai/dsh-web-search-deepseek`、`@deepseek-ai/dsh-web-deepseek-api` 和 `@deepseek-ai/dsh-web-fetch-http`。
|
||||
3. `@deepseek-ai/dsh-tool-web`(`packages/web/tool-web`)拥有面向模型的 `web_search` 和 `web_fetch` 工具 schema、提示词段落、参数校验、结果格式化,以及通过 `ctx.web` 实现的工具展示。
|
||||
|
||||
提供方不注册工具。提供方注册能力。`dsh-tool-web` 是面向模型的名称、描述、提示词引导、JSON Schema、展示的唯一所有者。
|
||||
@@ -32,9 +32,9 @@ Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13
|
||||
- 工具绝不会仅仅因为其选定的提供方缺失、配置错误、缺少凭证、存在歧义或暂时不可用就被注销。
|
||||
- 提供方在执行时解析,当选定的能力无法运行时返回结构化的 `WebError`。
|
||||
|
||||
这使模型 schema 保持稳定,而不将插件加载顺序、凭证状态或 HMR(热模块替换)时序纳入面向模型的约定。如果 web 搜索已启用但不存在可用的搜索提供方,`web_search` 仍然可见,执行时以结构化的 `WebError`(如 `WEB_PROVIDER_UNAVAILABLE` 或 `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)失败。发行版的 `dsh-tool-web` 按模型请求收窄了这一点:选中的提供方报告该请求的搜索无法鉴权时,它会[撤下 `web_search`](2026-09-27-web-search-offered-by-provider-readiness.zh.md)。如果某个提供方在 `dsh-tool-web` 之后出现,下一次执行即可使用它而无需更改 schema。如果某个提供方在调用过程中消失,执行以结构化的 `WebError` 失败,而不是静默选择另一个提供方或回退到 `UNKNOWN_TOOL`。
|
||||
这使模型 schema 保持稳定,而不将插件加载顺序、凭证状态或 HMR(热模块替换)时序纳入面向模型的约定。如果 web 搜索已启用但不存在可用的搜索提供方,`web_search` 仍然可见,执行时以结构化的 `WebError`(如 `WEB_PROVIDER_UNAVAILABLE` 或 `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)失败。`dsh-tool-web` 按模型请求收窄了这一点:选中的提供方报告该请求的操作无法鉴权时,它会[撤下 `web_search` 或 `web_fetch`](2026-09-27-web-search-offered-by-provider-readiness.zh.md)。如果某个提供方在 `dsh-tool-web` 之后出现,下一次执行即可使用它而无需更改 schema。如果某个提供方在调用过程中消失,执行以结构化的 `WebError` 失败,而不是静默选择另一个提供方或回退到 `UNKNOWN_TOOL`。
|
||||
|
||||
该 seam 刻意不暴露任何观察面——没有注册表变更事件,也没有聚合的能力状态查询。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。
|
||||
该 seam 刻意不暴露可用性观察——没有聚合的能力状态查询,也没有逐个提供方的可用性报告。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。提供方选择器消费的是注册情况而非可用性:`listProviders()` 与 Remote 方法 `web/listProviders` 列出每个已注册提供方及其可选的 `title` 与 `description`(包括不可用的提供方),且每次注册表变更后都会触发无载荷的 `web/providers-changed` 事件。[Web 提供方选择决策](2026-10-29-web-provider-selection.zh.md)记录了为何注册情况可观察而可用性不可观察。
|
||||
|
||||
## 包拓扑
|
||||
|
||||
@@ -43,11 +43,9 @@ Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13
|
||||
依赖方向与 bash 和 filesystem 一致:
|
||||
|
||||
```text
|
||||
@deepseek-ai/dsh-tool-web --depends on--> @deepseek-ai/dsh-web <--depends on-- @deepseek-ai/dsh-web-search-exa
|
||||
@deepseek-ai/dsh-tool-web --depends on--> @deepseek-ai/dsh-web <--depends on-- @deepseek-ai/dsh-web-search-deepseek
|
||||
consumer interface implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-search-perplexity
|
||||
implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-search-deepseek
|
||||
<--depends on-- @deepseek-ai/dsh-web-deepseek-api
|
||||
implementation
|
||||
<--depends on-- @deepseek-ai/dsh-web-fetch-http
|
||||
implementation
|
||||
@@ -57,9 +55,7 @@ Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
exa["@deepseek-ai/dsh-web-search-exa"] -->|registerSearchProvider| web["@deepseek-ai/dsh-web / ctx.web"]
|
||||
perplexity["@deepseek-ai/dsh-web-search-perplexity"] -->|registerSearchProvider| web
|
||||
deepseek["@deepseek-ai/dsh-web-search-deepseek"] -->|registerSearchProvider| web
|
||||
deepseek["@deepseek-ai/dsh-web-search-deepseek"] -->|registerSearchProvider| web["@deepseek-ai/dsh-web / ctx.web"]
|
||||
deepseekApi["@deepseek-ai/dsh-web-deepseek-api"] -->|registerSearchProvider / registerFetchProvider| web
|
||||
fetchLocal["@deepseek-ai/dsh-web-fetch-http"] -->|registerFetchProvider| web
|
||||
toolWeb["@deepseek-ai/dsh-tool-web"] -->|search/fetch| web
|
||||
@@ -67,9 +63,9 @@ flowchart LR
|
||||
toolWeb -->|ctx.tools.register| webFetch["tool: web_fetch"]
|
||||
```
|
||||
|
||||
`@deepseek-ai/dsh-web` 仅依赖 Cordis 和底层 harness 支持。它声明 `ctx.web`、提供方接口、请求/结果类型、提供方可用性约定和错误码。它不导入工具、agent(智能体)、会话、LLM 或提供方包。
|
||||
`@deepseek-ai/dsh-web` 仅依赖 Cordis 和底层 harness 支持,其中包括其 Remote 方法所用的 Typert 协议。它声明 `ctx.web`、提供方接口、请求/结果类型、提供方可用性约定和错误码。它不导入工具、agent(智能体)、会话、LLM 或提供方包。
|
||||
|
||||
提供方包仅依赖 `dsh-web` 和 Cordis。它们拥有凭证、端点、协议格式映射、解析和 `WebError` 转换,使用平台 `fetch`。每个提供方注入共享服务并注册后端;只有 `dsh-web` 拥有 `ctx.web` 键。提供方私有的协议形状不会产生对 `ctx.llm` 或 Cordis HTTP 服务的依赖。
|
||||
提供方包依赖 `dsh-web`、Cordis 及其凭据和配置函数。它们拥有凭证、端点、协议格式映射、解析和 `WebError` 转换,使用平台 `fetch`。每个提供方注入共享服务并注册后端;只有 `dsh-web` 拥有 `ctx.web` 键。提供方私有的协议形状不会产生对 `ctx.llm` 或 Cordis HTTP 服务的依赖。
|
||||
|
||||
`@deepseek-ai/dsh-tool-web` 依赖 `@deepseek-ai/dsh-web`、`@deepseek-ai/dsh-tools`、`@deepseek-ai/dsh-system-prompt` 和 Cordis。它从不导入具体的提供方包。
|
||||
|
||||
@@ -78,16 +74,22 @@ flowchart LR
|
||||
`ctx.web` 是一个提供方注册表加上一个带提供方选择的执行 API。注册表部分与 `LlmRuntime` 保持接近:每种能力类别一个 `Map<id, provider>`,`registerSearchProvider`/`registerFetchProvider` 方法返回 disposer,重复 id 抛出 `WebError`,执行时解析在选定提供方缺失或不可用时抛出异常。权威签名见 `packages/web/web/src/types.ts`;seam 的形状:
|
||||
|
||||
```ts
|
||||
import type { WebFetchRequest, WebFetchResult, WebSearchRequest, WebSearchResult } from '@deepseek-ai/dsh-web'
|
||||
import type {
|
||||
WebFetchRequest, WebFetchResult, WebProviderInfo, WebProviderList, WebSearchRequest, WebSearchResult,
|
||||
} from '@deepseek-ai/dsh-web'
|
||||
|
||||
interface WebSearchProvider {
|
||||
readonly id: string
|
||||
readonly title?: string
|
||||
readonly description?: WebProviderInfo['description']
|
||||
available(): boolean
|
||||
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
|
||||
}
|
||||
|
||||
interface WebFetchProvider {
|
||||
readonly id: string
|
||||
readonly title?: string
|
||||
readonly description?: WebProviderInfo['description']
|
||||
available(): boolean
|
||||
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
|
||||
}
|
||||
@@ -95,6 +97,7 @@ interface WebFetchProvider {
|
||||
interface WebRuntime {
|
||||
registerSearchProvider(provider: WebSearchProvider): () => void
|
||||
registerFetchProvider(provider: WebFetchProvider): () => void
|
||||
listProviders(): WebProviderList
|
||||
|
||||
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
|
||||
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
|
||||
@@ -131,18 +134,15 @@ interface WebRuntime {
|
||||
- id: web
|
||||
name: '@deepseek-ai/dsh-web'
|
||||
config:
|
||||
searchProvider: exa
|
||||
searchProvider: deepseek-search-api
|
||||
fetchProvider: http
|
||||
|
||||
- id: web-search-exa
|
||||
name: '@deepseek-ai/dsh-web-search-exa'
|
||||
|
||||
- id: web-search-perplexity
|
||||
name: '@deepseek-ai/dsh-web-search-perplexity'
|
||||
|
||||
- id: web-search-deepseek
|
||||
name: '@deepseek-ai/dsh-web-search-deepseek'
|
||||
|
||||
- id: web-deepseek-api
|
||||
name: '@deepseek-ai/dsh-web-deepseek-api'
|
||||
|
||||
- id: web-fetch-http
|
||||
name: '@deepseek-ai/dsh-web-fetch-http'
|
||||
|
||||
@@ -150,9 +150,9 @@ interface WebRuntime {
|
||||
name: '@deepseek-ai/dsh-tool-web'
|
||||
```
|
||||
|
||||
运维覆盖走同一条显式选择路径:`DSH_WEB_SEARCH_PROVIDER=perplexity` 等同于配置 `searchProvider: perplexity`,而非 `dsh-tool-web` 内部的隐式优先级链。
|
||||
运维覆盖走同一条显式选择路径:`DSH_WEB_SEARCH_PROVIDER=deepseek-search-api` 等同于配置 `searchProvider: deepseek-search-api`,而非 `dsh-tool-web` 内部的隐式优先级链。
|
||||
|
||||
每次搜索或抓取在执行时读取配置的 id,因此保存的设置变更无需重新挂载 `dsh-web` 即对下一次操作选择另一个提供方。网页搜索设置页的开关借此把两种能力同时切换到 [DeepSeek Search 与 Fetch API](../feature/2026-10-15-deepseek-search-fetch-api.zh.md)。
|
||||
每次搜索或抓取在执行时读取配置的 id,因此保存的设置变更无需重新挂载 `dsh-web` 即可让下一次操作选用新提供方。「网页搜索与获取」设置页的 Search 和 Fetch 下拉框借此分别切换各能力的提供方(包括 [DeepSeek Search 与 Fetch API](../feature/2026-10-15-deepseek-search-fetch-api.zh.md));[Web 提供方选择决策](2026-10-29-web-provider-selection.zh.md)负责内置默认值。
|
||||
|
||||
`ctx.web.search()` 和 `ctx.web.fetch()` 在执行时按上述选择规则解析提供方。如果选定的能力不可用,它们抛出带有结构化代码的 `WebError`,如 `WEB_PROVIDER_UNAVAILABLE`、`WEB_PROVIDER_CONFIGURED_MISSING`、`WEB_PROVIDER_CONFIGURED_UNAVAILABLE` 或 `WEB_PROVIDER_AMBIGUOUS`。如果未显式配置提供方且不存在可用提供方,执行错误是通用的 `WEB_PROVIDER_UNAVAILABLE` 情况;刻意不提供对每个不可用提供方的诊断汇总。
|
||||
|
||||
@@ -168,8 +168,8 @@ interface WebRuntime {
|
||||
|
||||
- `dsh-tool-web` 拥有该值并将其放在 `WebSearchRequest.maxResults` 上。
|
||||
- `ctx.web` 将请求原样传递给选定的提供方。
|
||||
- 当提供方的 API 支持结果数量控制时(Exa 的 `numResults`),提供方在请求层应用 `maxResults`,作为成本/延迟优化。
|
||||
- `ctx.web` 在结果上强制执行上限:如果提供方返回的 source 数量超过 `maxResults`——因为其 API 没有结果数量控制(Perplexity)或忽略了提示——seam 将 `sources[]` 截断到 `maxResults` 并在返回前将 `WebSearchResult.truncated` 设为 `true`。这使上限成为面向模型层可以依赖的单一跨提供方保证,而非每个提供方都必须记得遵守的东西。
|
||||
- 当提供方的 API 支持结果数量控制时(DeepSeek Search API 的 `count`),提供方在请求层应用 `maxResults`,作为成本/延迟优化。
|
||||
- `ctx.web` 在结果上强制执行上限:如果提供方返回的 source 数量超过 `maxResults`——因为其 API 没有结果数量控制或忽略了提示——seam 将 `sources[]` 截断到 `maxResults` 并在返回前将 `WebSearchResult.truncated` 设为 `true`。这使上限成为面向模型层可以依赖的单一跨提供方保证,而非每个提供方都必须记得遵守的东西。
|
||||
|
||||
seam 请求不携带提供方特有的控制——没有 Perplexity 模型选择、搜索时效性、域名过滤器、Exa `livecrawl`、Exa `type`、区域提示、生成式回答预算或搜索深度。只有当某个字段具有提供方无关的语义,且工具 schema 和选定的提供方都能诚实地遵守时,才会添加。
|
||||
|
||||
@@ -190,13 +190,14 @@ interface WebSearchSource {
|
||||
readonly url: string
|
||||
readonly title?: string
|
||||
readonly snippet?: string
|
||||
readonly summary?: string
|
||||
readonly publishedAt?: string
|
||||
}
|
||||
```
|
||||
|
||||
`content` 是可选的提供方生成的回答文本、搜索上下文或摘要。`sources[]` 是可移植的引用结构。source 必有 URL;title、snippet 和 `publishedAt` 可选,因为并非每个提供方都返回它们。`title` 不是必填:Perplexity 风格的引用可能只提供 URL,强制适配器编造标题会让 seam 说谎。`dsh-tool-web` 渲染 `title ?? hostname(url)` 风格的回退标签用于展示。`publishedAt` 是可选的发布/抓取时间戳,为 ISO-8601 字符串——Exa 在每条结果上以 `publishedDate` 返回它,Perplexity 在搜索结果上返回 `date`,因此它是真实的提供方数据而非派生值;seam 以字符串形式传递,日期解析留给消费方。
|
||||
`content` 是可选的提供方生成的回答文本、搜索上下文或摘要。`sources[]` 是可移植的引用结构。source 必有 URL;title、snippet、summary 和 `publishedAt` 可选,因为并非每个提供方都返回它们。`snippet` 是用于展示的短摘录,`summary` 是供模型阅读的较长摘录,其长度由消费方限制。`title` 不是必填:Perplexity 风格的引用可能只提供 URL,强制适配器编造标题会让 seam 说谎。`dsh-tool-web` 渲染 `title ?? hostname(url)` 风格的回退标签用于展示。`publishedAt` 是可选的发布/抓取时间戳,为 ISO-8601 字符串——DeepSeek Search API 在每条结果上以可为 null 的 `date` 返回它,Exa 以 `publishedDate` 返回,Perplexity 以 `date` 返回,因此它是真实的提供方数据而非派生值;seam 以字符串形式传递,日期解析留给消费方。
|
||||
|
||||
Exa 搜索将提供方扁平 `results[]` 的每一项映射为 `WebSearchSource`:`url` ← `url`、`title` ← `title`、`snippet` ← 第一个 `highlights[]` 条目(没有 highlight 的条目没有可移植的 snippet,被丢弃)、`publishedAt` ← `publishedDate`。Exa 不返回提供方生成的回答,因此 `content` 省略。Perplexity 搜索将 `choices[0].message.content` 映射为 `content`,并优先使用结构化的顶层 `search_results[]` 作为 `sources[]`——`url` ← `url`、`title` ← `title`、`snippet` ← `snippet`(常为空)、`publishedAt` ← `date`——仅在 `search_results` 缺失时回退到纯 URL 的 `citations[]` 数组(这些 source 只有 `url`)。如果提供方返回的结构化字段少于 seam 支持的,适配器省略那些可选字段。
|
||||
DeepSeek Search API 提供方将响应 `results[]` 的每一项映射为 `WebSearchSource`:`url` ← `url`、`title` ← `title`、`snippet` ← `snippet`、`summary` ← `summary`、`publishedAt` ← `date`,空值省略。它不返回提供方生成的回答,因此 `content` 省略。如果提供方返回的结构化字段少于 seam 支持的,适配器省略那些可选字段。
|
||||
|
||||
完整页面获取仍是 `web_fetch(url)` 的职责。搜索 snippet 是发现上下文,不是获取到的页面正文。
|
||||
|
||||
@@ -283,7 +284,7 @@ fetch 提供方的资源控制:
|
||||
|
||||
## 测试
|
||||
|
||||
每一层在自己的边界处固定:`dsh-web` 中的注册/选择/截断/abort 约定与 `WebError` 码;每个提供方基于录制的 fixture(测试前置数据)的请求/响应映射(Perplexity fixture 包含纯 URL 引用,以保持可选 source 字段的诚实性),加上每个真实提供方的自跳过带密钥冒烟测试;`web-fetch-http` 中的真实本地 HTTP 行为;`dsh-tool-web` 中通过真实工具注册表的启用驱动注册、结构化执行错误和结果格式化。一个真实 Loader 冒烟测试守护两种导出形状([事故复盘(postmortem) 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.zh.md)):`dsh-web` 是默认导出的服务,而提供方和 `tool-web` 是命名空间插件,误加 `export default` 会丢失 `inject`。
|
||||
每一层在自己的边界处固定:`dsh-web` 中的注册/选择/截断/abort 约定与 `WebError` 码;每个提供方基于录制的 fixture(测试前置数据)的请求/响应映射,加上每个真实提供方的自跳过带密钥冒烟测试;`web-fetch-http` 中的真实本地 HTTP 行为;`dsh-tool-web` 中通过真实工具注册表的启用驱动注册、结构化执行错误和结果格式化。一个真实 Loader 冒烟测试守护两种导出形状([事故复盘(postmortem) 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.zh.md)):`dsh-web` 是默认导出的服务,而提供方和 `tool-web` 是命名空间插件,误加 `export default` 会丢失 `inject`。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -293,7 +294,7 @@ fetch 提供方的资源控制:
|
||||
|
||||
### 将提供方调度直接放在 `dsh-tool-web` 中
|
||||
|
||||
这类似 OpenCode 的本地 web 搜索:一个稳定的 `websearch` 工具在内部调度到 Exa 或 Parallel。对于小型产品路径可以接受,但作为 harness 基础是错误的。工具包将拥有提供方选择、凭证、请求映射、传输、响应解析和展示,使得在不将 Exa 和 Perplexity 的差异烘焙进工具 schema 的情况下难以添加它们。
|
||||
这类似 OpenCode 的本地 web 搜索:一个稳定的 `websearch` 工具在内部调度到 Exa 或 Parallel。对于小型产品路径可以接受,但作为 harness 基础是错误的。工具包将拥有提供方选择、凭证、请求映射、传输、响应解析和展示,使得在不将各提供方格式差异烘焙进工具 schema 的情况下难以添加格式不同的提供方。
|
||||
|
||||
### 将搜索和 fetch 拆为两个 seam(`dsh-search`、`dsh-fetch`)
|
||||
|
||||
@@ -322,9 +323,9 @@ fetch 提供方的资源控制:
|
||||
|
||||
## 后果
|
||||
|
||||
**搜索 schema 刻意精简。** Exa 和 Perplexity 都暴露了有用的提供方特有控制;只有当某个控制能以提供方无关的方式定义、且工具注册和提供方执行都能诚实遵守时,才会添加。
|
||||
**搜索 schema 刻意精简。** 各搜索 API 暴露了有用的提供方特有控制,例如 Exa 的 `livecrawl`、Perplexity 的搜索时效性和 DeepSeek Search API 的 `localization`;只有当某个控制能以提供方无关的方式定义、且工具注册和提供方执行都能诚实遵守时,才会添加。
|
||||
|
||||
**Perplexity 引用可能稀疏。** 一条引用可能只有 URL。将 `title` 和 `snippet` 设为可选使 seam 保持诚实,但意味着 `tool-web` 需要渲染回退标签。
|
||||
**引用可能稀疏。** 一条 Perplexity 风格的引用可能只有 URL。将 `title` 和 `snippet` 设为可选使 seam 保持诚实,但意味着 `tool-web` 需要渲染回退标签。
|
||||
|
||||
**稳定的工具注册将配置错误推迟到执行时。** 当产品启用了 web 访问时,保持工具可见是正确的;但期望 web 搜索可用的产品应用应当明确暴露结构化的 `WEB_PROVIDER_CONFIGURED_MISSING`/`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`/`WEB_PROVIDER_AMBIGUOUS` 失败,避免用户直到模型调用工具后才发现配置问题。
|
||||
|
||||
@@ -340,7 +341,7 @@ fetch 提供方的资源控制:
|
||||
|
||||
- `pdf` `WebFetchBody` 类别:`http` 提供方将可文本提取的 PDF 解码(尽力而为、有上限、`truncated`)为 `{ kind: 'pdf'; content; pageCount? }` 分支,`tool-web` 渲染它。这是 fetch 而非 `web_extract`——PDF 获取是具体的 HTTP 200 加确定性的本地解码,不是提供方侧对非 HTTP 资源的提取。添加它是跨 `dsh-web`(声明分支)、提供方(解码 + 将「二进制拒绝」收窄为「拒绝二进制,但可文本提取的 PDF 除外」;需要 OCR 的扫描/图片 PDF 不在范围内)和 `tool-web`(渲染)的协调变更。封闭的 `WebFetchBody` 联合类型使消费方在新分支被处理之前编译失败。
|
||||
- 提供方支撑的提取作为独立的 `web_extract` 能力,而非静默扩展 `web_fetch`。
|
||||
- `query` 和 `maxResults` 之外的提供方无关搜索控制,待 Exa 和 Perplexity 都能诚实遵守时再添加。
|
||||
- `query` 和 `maxResults` 之外的提供方无关搜索控制,待已注册的提供方都能诚实遵守时再添加。
|
||||
|
||||
## 开放问题
|
||||
|
||||
|
||||
+4
-4
@@ -18,11 +18,11 @@
|
||||
en: ce28268021a38b89
|
||||
zh: 7802540e4ef04752
|
||||
/agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/decision/component-props-four-shares-each-from-its-own-source-of-truth:
|
||||
en: f3fca569fa0c5168
|
||||
zh: 647a82662f6a887d
|
||||
en: c1892cb7309c0824
|
||||
zh: 21983594d0c7f24f
|
||||
/agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/decision/the-chain-kind-entries-self-nominate-first-match-renders:
|
||||
en: 56278be7f3688805
|
||||
zh: 16602ffbf4baef85
|
||||
en: dad15e2e1309c9a4
|
||||
zh: ee2d13b27c92a73b
|
||||
/agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/decision/the-store-seat-framework-engine-registrant-schema:
|
||||
en: 2645acc9d29c20ec
|
||||
zh: a8a2fd293ef5da04
|
||||
|
||||
@@ -50,12 +50,13 @@ A contributor whose activation order is independent from the declaring entry use
|
||||
| child render | `PropsRenderSlots<S>` | register's `children` keys | `renderSlot(key, owner)`, key statically narrowed to S; chain keys add `renderSlotChain` |
|
||||
| store | `PropsStore<H>` | store factory return type | `useStore` selector hook + `actions.*` (draft-param stripped) |
|
||||
| business | `I` | inject return type | plain data + callbacks; a reserved `hooks` compartment of bare observables arrives bound as `use<Name>` selector hooks (`InjectFace<I>`) |
|
||||
| kind-specific | `MatchedShare` / `HocShare` | SlotMap entry's kind | chain `matched`; hoc `children`, the next inner layer or the owner content |
|
||||
|
||||
`sessionId` is framework-supplied wherever `scope: 'session'` is declared — owner params do not carry it. The register call site is the double-lock choke point: a component whose renderSlot keys exceed the `children` declaration, or that misses a declared face, or whose store/inject shapes drift, is a compile error on that line. Delegation is ordinary props passing (hand the `renderSlot` function down, optionally behind a narrower signature) — there is no whitelist face object and no minting API.
|
||||
|
||||
### The chain kind: entries self-nominate, first match renders
|
||||
|
||||
The fourth `SlotKind`, `'chain'`, inverts routing authority relative to `keyed`: a keyed dispatch site picks its occupant by `entryKey`, while a chain entry nominates itself — the owner dispatches one common currency of owner props and never learns who takes over, so a new takeover package registers with zero owner edits. A chain registration carries a `select` pure selector (`ChainSelect<O, M>`: `(owner) => matched | null`) and an optional `priority` (ascending; ties keep registration = assembly order — the deployment-controllable inject topology — under the same stable sort as list `order`); registering without `select` is one of the loud-at-load cases above. At render, the outlet runs the selectors in chain order: the first non-null return elects its entry and the returned value joins the component's props as `matched` (the component never re-derives its own match), `null` passes the turn to the next entry, and all-null renders the owner's fallback body (`ChainRenderOpts`).
|
||||
A fifth kind, `'hoc'`, nests entries around owner-supplied `children` and adds a kind-specific `children` prop the same way `chain` adds `matched`; its decision lives in the [hoc slot kind note](2026-11-03-hoc-slot-kind.md). The fourth `SlotKind`, `'chain'`, inverts routing authority relative to `keyed`: a keyed dispatch site picks its occupant by `entryKey`, while a chain entry nominates itself — the owner dispatches one common currency of owner props and never learns who takes over, so a new takeover package registers with zero owner edits. A chain registration carries a `select` pure selector (`ChainSelect<O, M>`: `(owner) => matched | null`) and an optional `priority` (ascending; ties keep registration = assembly order — the deployment-controllable inject topology — under the same stable sort as list `order`); registering without `select` is one of the loud-at-load cases above. At render, the outlet runs the selectors in chain order: the first non-null return elects its entry and the returned value joins the component's props as `matched` (the component never re-derives its own match), `null` passes the turn to the next entry, and all-null renders the owner's fallback body (`ChainRenderOpts`).
|
||||
|
||||
The decline decision lives in `select`, never in a mounted component probing its own props: a component that mounts only to render null still runs its hooks and effects for nothing, and the resulting mount/unmount churn breaks memoization and React key semantics, whereas a selector is a pure function — unit-testable, zero mount side effects — the same discipline as "presentation methods are pure functions of `args`". Purity is the selector's contract: it reads no external mutable state and produces no side effects, so the routing decision is entirely a function of the owner props and safe to run on every dispatch. Selectors route; they never mint — per-dispatch object construction would churn identity every render, so wrapping a matched value in a richer face happens inside the elected component (`useMemo` keyed on `matched`).
|
||||
|
||||
|
||||
+2
-1
@@ -50,12 +50,13 @@ ctx.slots.register({
|
||||
| 子 slot 渲染 | `PropsRenderSlots<S>` | register 的 `children` 键集 | `renderSlot(key, owner)`,键参静态收窄到 S;chain 键另有 `renderSlotChain` |
|
||||
| store | `PropsStore<H>` | store 工厂的返回类型 | `useStore` selector 钩子 + `actions.*`(剥去 draft 形参) |
|
||||
| 业务 | `I` | inject 的返回类型 | 普通数据+回调;保留的 `hooks` 区域内,裸 observable 经绑定后以 `use<Name>` 选择器钩子的形式到达(`InjectFace<I>`) |
|
||||
| 按 kind | `MatchedShare` / `HocShare` | SlotMap entry 的 kind | chain 的 `matched`;hoc 的 `children`,即内一层或宿主内容 |
|
||||
|
||||
凡声明 `scope: 'session'` 之处,`sessionId` 一律由框架供给——owner 传参不携带它。register 调用点是双重类型约束的收口:组件的 renderSlot 键集超出 `children` 声明、漏接某个已声明的面、store/inject 形状漂移,任何一条都在那一行上报编译错误。转授就是普通的 props 传递(把 `renderSlot` 函数递下去,可按需包一层更窄的签名)——不存在白名单面对象,也不存在铸面 API。
|
||||
|
||||
### chain kind:entry 自荐,首个匹配项负责渲染
|
||||
|
||||
第四种 `SlotKind`——`'chain'`——把路由权相对 `keyed` 反转:keyed 的分派现场以 `entryKey` 点选占用 slot 的 entry,chain 则由 entry 自荐——owner 只分派一套格式统一的 owner props,永远不知道谁来接管,新的接管包注册进来 owner 零改动。chain 注册携带一个 `select` 纯选择器(`ChainSelect<O, M>`:`(owner) => matched | null`)与可选的 `priority`(升序;同值保持注册序 = 装配序——部署可控的 inject 拓扑——复用 list `order` 的同一稳定排序);注册缺 `select` 即上文装载即炸情形之一。渲染时 outlet 按链序依次执行各 select:首个非 null 返回值当选,该值以 `matched` 并入组件 props(组件绝不自行重新推导匹配);返回 `null` 则轮到下一个 entry;全 null 则渲染 owner 的 fallback 体(`ChainRenderOpts`)。
|
||||
第五种 kind `'hoc'` 把 entry 层层嵌套在 owner 提供的 `children` 外面,并像 `chain` 提供 `matched` 那样提供按 kind 的 `children` prop;其决策见 [hoc 插槽类型 Note](2026-11-03-hoc-slot-kind.zh.md)。第四种 `SlotKind`——`'chain'`——把路由权相对 `keyed` 反转:keyed 的分派现场以 `entryKey` 点选占用 slot 的 entry,chain 则由 entry 自荐——owner 只分派一套格式统一的 owner props,永远不知道谁来接管,新的接管包注册进来 owner 零改动。chain 注册携带一个 `select` 纯选择器(`ChainSelect<O, M>`:`(owner) => matched | null`)与可选的 `priority`(升序;同值保持注册序 = 装配序——部署可控的 inject 拓扑——复用 list `order` 的同一稳定排序);注册缺 `select` 即上文装载即炸情形之一。渲染时 outlet 按链序依次执行各 select:首个非 null 返回值当选,该值以 `matched` 并入组件 props(组件绝不自行重新推导匹配);返回 `null` 则轮到下一个 entry;全 null 则渲染 owner 的 fallback 体(`ChainRenderOpts`)。
|
||||
|
||||
「不接」的判定住在 `select` 里,绝不在挂载后的组件里自探 props:组件为了渲染 null 也得先挂载,其钩子与 effect 全部白跑,随之而来的挂载/卸载抖动还会破坏 memo 化与 React key 语义;而选择器是纯函数——可单测、零挂载副作用——与「presentation methods are pure functions of `args`」是同一条纪律。纯,就是选择器的约定:不读外部可变状态、不产副作用,路由判定因此完全是 owner props 的函数,每次分派都可安全执行。选择器只做路由,绝不创建新对象——按分派逐次构造对象会让引用每次渲染都换新;把匹配值包成更丰富的面这件事,发生在当选组件内部(以 `matched` 为依赖的 `useMemo`)。
|
||||
|
||||
|
||||
+4
-4
@@ -21,8 +21,8 @@
|
||||
en: 0a4240d88cca240a
|
||||
zh: 8f5f1527caf571cd
|
||||
/agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload/decision/the-loading-flow-end-to-end:
|
||||
en: 15fb56befd328339
|
||||
zh: 0ffb10ada7e52805
|
||||
en: dd05aa1ee608c414
|
||||
zh: 2863ced0086e3122
|
||||
/agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload/decision/live-graph-reconciliation:
|
||||
en: 37e2fa1fe5cb2b95
|
||||
zh: 80e513db87709cb4
|
||||
@@ -33,8 +33,8 @@
|
||||
en: 94ca0d09e6fd1e3a
|
||||
zh: d5949a2bdec795ba
|
||||
/agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload/consequences:
|
||||
en: 16e1f2467c559315
|
||||
zh: a714ec6ff2722cf1
|
||||
en: 624efdd136029771
|
||||
zh: 7d60207147e631fe
|
||||
/agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload/alternatives-considered:
|
||||
en: b8f036f50cff7cd8
|
||||
zh: 6d623c4a1f4314b1
|
||||
@@ -67,8 +67,8 @@ Why is the roster yml rows and not a scan? Because which plugins compose into a
|
||||
1. The kernel mounts the vendored Loader and injects the module system as `internal` before any entry exists. Ordering matters: `tree.import`'s bare-import fallback must never run in a browser.
|
||||
2. It creates every graph row uniformly. Importing the modules row returns the memoized bootstrap exports, whose `apply()` reads that tree's `Loader.internal` and provides the same instance as `ctx.modules`; rows that require that service remain PENDING until then, so the modules row needs no special creation position. Render assembly is an ordinary host-graph row provided by `dsh-client-ui-renderer`; the kernel appends no assembly pseudo-entry.
|
||||
3. Graph order governs synchronous factory availability; Cordis activation remains independent and proceeds through service waiting.
|
||||
4. `settled` = every entry created + `loader.await()` quiescent + an all-ACTIVE sweep. The sweep lists each import-failed, FAILED, or PENDING fiber with its missing services. It exists because cordis inject waits have no timeout — the sweep is the fail-loud floor.
|
||||
5. The framework-free loading page projects real fiber states via `internal/status`. After the sweep, the kernel calls `ctx.uiRenderer.mount(container)` and replaces the page with the real UI in one pass.
|
||||
4. `settled` = every entry created + `loader.await()` quiescent + the boot audit. The audit lists each import-failed, FAILED, or PENDING fiber with its activation error or missing services. It exists because cordis inject waits have no timeout. An inactive entry on the browser required list rejects startup; other inactive entries produce one console warning ([startup policy](2026-09-09-consumer-owned-startup-strictness.md)).
|
||||
5. The framework-free loading page projects real fiber states via `internal/status`. After the audit accepts the tree, the kernel calls `ctx.uiRenderer.mount(container)` and replaces the page with the real UI in one pass.
|
||||
|
||||
### Live graph reconciliation
|
||||
|
||||
@@ -104,7 +104,7 @@ The current package inventory and build forms live in the [client shell layering
|
||||
|
||||
One governance implementation runs on both sides of the wire; the browser-specific layer is one module system plus one reload plugin. Dynamic packages have one artifact form, so the purity check covers them all. Cordis dependencies, module requests, and the boot tier live with their owners — the manifests — while the composing app holds only the roster. Host graph validation and recursive request arrival keep synchronous factory dependencies explicit. Browser-native script loading preserves the standard mapping among plugin network resources, generated bundles, and TypeScript/TSX sources, while the module system keeps two replaceable carrier hooks: `loadBundle` for bundle bytes and `resolveAsset` for package-asset references.
|
||||
|
||||
Costs accepted: the vendored Loader carries idle machinery in the browser (EntryTree persistence is a no-op, groups/isolation unused); every plugin edit in dev pays a bundle rebuild plus fiber remount; graph `inject` rows guide factory arrival but service availability remains the activation authority, so a mismatch appears at the settled sweep; the static UI libraries keep direct value exports; every bundle gains a source-map artifact; and external-script failures provide only coarse URL diagnostics instead of the HTTP status available to an explicit fetch. The Host retains bundle snapshots and lazy response plans for the current graph and one prior startup generation. Script and map bodies are cached after their first `GET`, so memory grows with bundle snapshots and requested response bodies. Each materialized response stays fixed at its URL; a prior-generation map first requested after a rebuild reads the current map file.
|
||||
Costs accepted: the vendored Loader carries idle machinery in the browser (EntryTree persistence is a no-op, groups/isolation unused); every plugin edit in dev pays a bundle rebuild plus fiber remount; graph `inject` rows guide factory arrival but service availability remains the activation authority, so a mismatch appears at the boot audit; the static UI libraries keep direct value exports; every bundle gains a source-map artifact; and external-script failures provide only coarse URL diagnostics instead of the HTTP status available to an explicit fetch. The Host retains bundle snapshots and lazy response plans for the current graph and one prior startup generation. Script and map bodies are cached after their first `GET`, so memory grows with bundle snapshots and requested response bodies. Each materialized response stays fixed at its URL; a prior-generation map first requested after a rebuild reads the current map file.
|
||||
|
||||
Roster: it lives in the web bundle's config tree (`packages/bundle/web-app/cordis.patch.yml`); `mountWebPlugins` and the `CLIENT_PACKAGES` constant are gone, and recomposing a deployment means swapping the yml/overlay. The graph composer lives in the `dsh-client-modules` node half, while the parser-preloaded client face bootstraps the browser module table. The webserver remains a plain route-registration plugin; `/api/*` binding, browser authentication, RPC envelopes, and exact Fetch routes belong to the Connection node half, while Remote dispatch belongs to API Gateway and the dev bundle watch plus SSE channel belongs to the hmr node half.
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ Host 会快照每个已构建插件入口 bundle,并把每个调度阶段的
|
||||
1. 内核挂载 vendored Loader,在任何 entry 存在之前就把模块系统注入为 `internal`。顺序有讲究:`tree.import` 的裸 import 兜底分支在浏览器里绝不能跑到。
|
||||
2. 它统一创建每个 graph row。Import modules row 会返回记忆化的 bootstrap exports,其 `apply()` 读取该树的 `Loader.internal`,把同一个实例提供为 `ctx.modules`;需要该 service 的 row 会保持 PENDING 直至此时,因此 modules row 无需特殊创建位置。渲染组装是由 `dsh-client-ui-renderer` 提供的普通 host graph row;内核不追加组装伪 entry。
|
||||
3. Graph 顺序治理同步 factory 可用性;Cordis 激活与之独立,仍经服务等待推进。
|
||||
4. `settled` = 每个 entry 已创建 + `loader.await()` 完全停稳 + 一次全 ACTIVE 扫描。扫描列出每个 import 失败、FAILED 或 PENDING 的 fiber 及其缺失的服务。它存在的理由:cordis 的 inject 等待没有超时——这次扫描就是大声失败的兜底线。
|
||||
5. 不依赖框架的 loading 页经 `internal/status` 投影真实 fiber 状态。检查完成后,内核调用 `ctx.uiRenderer.mount(container)`,一次切换到真实 UI。
|
||||
4. `settled` = 每个 entry 已创建 + `loader.await()` 完全停稳 + 启动审计。审计列出每个 import 失败、FAILED 或 PENDING 的 fiber 及其激活错误或缺失的服务。它存在的理由:cordis 的 inject 等待没有超时。浏览器必需名单中的 entry 未激活会拒绝启动;其他未激活 entry 输出一条控制台警告([启动策略](2026-09-09-consumer-owned-startup-strictness.zh.md))。
|
||||
5. 不依赖框架的 loading 页经 `internal/status` 投影真实 fiber 状态。审计接受插件树后,内核调用 `ctx.uiRenderer.mount(container)`,一次切换到真实 UI。
|
||||
|
||||
### 动态图对账
|
||||
|
||||
@@ -104,7 +104,7 @@ Bootstrap 替换会在失效或卸载前被拒绝:模块系统保留其初始
|
||||
|
||||
Wire 两侧运行同一份治理实现;浏览器特有层只包含一套模块系统和一个重载插件。动态包只有一种产物形态,因此纯度检查覆盖全部动态包。Cordis 依赖、模块请求与启动档位都与其所有者——manifest——同住,负责组合的 app 只握名册。Host graph 校验与递归请求到达使同步 factory 依赖保持显式。浏览器原生 script 装载保留插件网络资源、生成 bundle 与 TypeScript/TSX 源码之间的标准映射,模块系统只保留两个可替换的载体钩子:负责 bundle 字节的 `loadBundle` 和负责包资源引用的 `resolveAsset`。
|
||||
|
||||
接受的代价:vendored Loader 在浏览器里背着闲置机件(EntryTree 持久化是 no-op,分组/隔离未用);开发期每次修改插件都要付一次 bundle 重建加 fiber 重挂;graph `inject` row 指导 factory 到达,但服务可用性仍是激活权威,因此不匹配会在 settled 扫描时浮出;静态 UI 库保留直接实体导出;每个 bundle 多出一份 sourcemap 产物,外部 script 失败也只能给出粗粒度 URL 诊断,不能像显式 fetch 那样报告 HTTP 状态。Host 为当前图及上一代启动图保留 bundle 快照与惰性响应计划。脚本和 map body 在首次 `GET` 后缓存,因此内存随 bundle 快照和已请求的响应 body 增长。每份已物化响应在其 URL 下保持固定;若上一代 map 在重建后才首次被请求,则会读取当前 map 文件。
|
||||
接受的代价:vendored Loader 在浏览器里背着闲置机件(EntryTree 持久化是 no-op,分组/隔离未用);开发期每次修改插件都要付一次 bundle 重建加 fiber 重挂;graph `inject` row 指导 factory 到达,但服务可用性仍是激活权威,因此不匹配会在启动审计时浮出;静态 UI 库保留直接实体导出;每个 bundle 多出一份 sourcemap 产物,外部 script 失败也只能给出粗粒度 URL 诊断,不能像显式 fetch 那样报告 HTTP 状态。Host 为当前图及上一代启动图保留 bundle 快照与惰性响应计划。脚本和 map body 在首次 `GET` 后缓存,因此内存随 bundle 快照和已请求的响应 body 增长。每份已物化响应在其 URL 下保持固定;若上一代 map 在重建后才首次被请求,则会读取当前 map 文件。
|
||||
|
||||
名册位于 web 组合包的配置树(`packages/bundle/web-app/cordis.patch.yml`);`mountWebPlugins` 与 `CLIENT_PACKAGES` 常量已消失,重组一次部署等于替换 yml/overlay。Graph 组合器位于 `dsh-client-modules` node 半,由 parser 预载的 Client face 则自举浏览器模块表。Webserver 继续作为朴素路由注册插件;`/api/*` 绑定、浏览器认证、RPC envelope 与精确 Fetch 路由属于 Connection node 半,Remote 分发属于 API Gateway,开发期 bundle 监视与 SSE 通道属于 HMR node 半。
|
||||
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@
|
||||
en: aea80ece13f2905b
|
||||
zh: a880bc1a7839cf12
|
||||
/agent-note-one-ordering-for-configuration-sources-and-what-a-discovered-file-may-not-decide/consequences:
|
||||
en: 640b7ee4e8c57ed0
|
||||
zh: 1c17b3fba16861a8
|
||||
en: 41ebe22077560090
|
||||
zh: da3475e4c196c3f9
|
||||
/agent-note-one-ordering-for-configuration-sources-and-what-a-discovered-file-may-not-decide/alternatives-considered:
|
||||
en: ac43499cfe7b8495
|
||||
zh: e5ab599e7d3ed753
|
||||
@@ -58,7 +58,6 @@ The line is that these take effect with no user action, before any turn, outside
|
||||
- A `.env` holding `DSH_*`, `PATH`, `BROWSER`, or — in the invoking directory — a proxy variable fails the launch instead of being applied. Developers keeping switches in a repository `.env` move them to their shell — a deliberate, loud break.
|
||||
- Composition is no longer overridable by a stale shell endpoint. It is still overridable by a user's stored `settings.yaml`, which is the settings seam's layering and not something this note changes; the product CLI offers no flag above it, so a deployment that must win against stored settings owns its own bin or loader tree.
|
||||
- Not solved: the layers are still materialized into `process.env`, so ordinary project variables continue to reach child processes under the subprocess scrub. Bootstrap variables cannot come from a file at all; the environment package records the remaining subprocess reach as a limitation.
|
||||
- Exa and Perplexity still capture their key at load time rather than through the credential seam. They no longer read raw `process.env` — they resolve through the trusted layers — but converting them to per-request credential resolution is separate work.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ inherited process environment (read-only, wins)
|
||||
- 含 `DSH_*`、`PATH`、`BROWSER` 或(在调用目录中)proxy 变量的 `.env` 会导致启动失败而不是被应用。把开关放在仓库 `.env` 里的开发者需要改放到 shell——这是一次刻意且响亮的破坏。
|
||||
- composition 不再会被陈旧的 shell endpoint 覆盖。但它仍然会被用户已存的 `settings.yaml` 覆盖,这是 settings seam 的分层方式,本 Note 不改变它;产品 CLI 没有高于它的标志,因此需要压过已存 settings 的部署方要自带 bin 或 loader 配置树。
|
||||
- 未解决的:各层仍然会被物化进 `process.env`,因此普通项目变量继续按子进程清洗规则抵达子进程。bootstrap 变量完全不能来自文件;环境包将其余变量仍可抵达子进程这一点记录为一项限制。
|
||||
- Exa 与 Perplexity 仍在加载时捕获密钥,而不是经凭据 seam。它们不再读裸 `process.env`——改为经受信层解析——但把它们改造成按请求解析凭据是另一件事。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: 29def175ab33b7d6
|
||||
zh: 87e55ec28c20c7c1
|
||||
/agent-note-package-and-update-the-electron-desktop-application/updates-and-recovery:
|
||||
en: 4cb7014acfe71762
|
||||
zh: 2178e0337cba0324
|
||||
en: 61a02870beb953ec
|
||||
zh: 205ecce330959e9a
|
||||
/agent-note-package-and-update-the-electron-desktop-application/security-and-release-policy:
|
||||
en: 5faaa53b58121311
|
||||
zh: e1c3fc8d21a27d1a
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron update uses one `electron-updater` release stream and signed `electron-
|
||||
|
||||
The [immediate-window reference](../../../../apps/desktop/README.md) owns the local loading page, direct Host startup, and recovery in the main window. Profile reconciliation follows the [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md).
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, version-derived channel metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. Stable versions use the `latest` metadata name; prereleases use the first semantic-version prerelease identifier. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations.
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, channel-selected metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. The selected channel determines the metadata name: `stable.yml` or `nightly.yml`, with `-mac` before the extension for macOS; version suffixes do not select the channel. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations.
|
||||
|
||||
Test releases require `DOWNLOAD_TEST_RELEASE_ID` from the platform dotenv file. Their feeds and binaries share `dsh-desk/<release-id>/`; production retains its fixed directories. The ID is part of the packaged update URL, so upload rejects a different ID. Internal distribution can supply a new installer after rotation; installed clients retain their existing feed. Random directories reduce guessing but provide no authorization for link holders. The [release configuration](../../../../apps/desktop/publishing/README.md#upload-updates) defines generation and reuse.
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b
|
||||
|
||||
[立即显示窗口参考](../../../../apps/desktop/README.zh.md)负责本地加载页、直接启动 Host 和主窗口恢复。profile 协调遵循[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)。
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据版本得出的频道元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。稳定版本使用 `latest` 元数据名称,预发布版本则使用语义化版本的第一个预发布标识符。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据所选渠道确定的元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。所选渠道决定元数据名称:`stable.yml` 或 `nightly.yml`,macOS 在扩展名前加 `-mac`;版本后缀不决定渠道。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
|
||||
|
||||
测试发布必须从平台 dotenv 文件提供 `DOWNLOAD_TEST_RELEASE_ID`。清单和安装包共用 `dsh-desk/<release-id>/`,生产环境保留固定目录。ID 是打包更新 URL 的一部分,因此上传会拒绝不同的 ID。轮换后可通过内部分发提供新安装包,已安装客户端保留原清单地址。随机目录降低被猜中的概率,但不对持有链接者进行鉴权。[发布配置](../../../../apps/desktop/publishing/README.zh.md#upload-updates)定义生成与复用方式。
|
||||
|
||||
|
||||
+14
-14
@@ -3,23 +3,23 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation:
|
||||
en: 758d31ae04ce0d4f
|
||||
zh: 112475efbd52b9f6
|
||||
en: 9d0af2bae38b1eec
|
||||
zh: 8a6e8502a5195548
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/problem:
|
||||
en: c988b8a3be4e6f19
|
||||
zh: b58011fd8d2e878e
|
||||
en: 5dc4c218f648c27e
|
||||
zh: b48531fac94b25db
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision:
|
||||
en: 1188c699ce6e6ca2
|
||||
zh: 4d5b974cd1216f06
|
||||
en: f4789878ac0e79b2
|
||||
zh: 68eb68d34cba810d
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision/one-model-tool-and-one-return-instruction:
|
||||
en: 4b0a25389324fa91
|
||||
zh: c2cdd9f7e40ef43b
|
||||
en: 62284bd7dd804188
|
||||
zh: b3a8dfebffc01bdb
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/decision/complete-removal-and-reintroduction-condition:
|
||||
en: 5a9ec6478cf7430c
|
||||
zh: f4c2f067cc72040e
|
||||
en: 8cd78e21602f17f0
|
||||
zh: 3843fea07e9b93a3
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/alternatives-considered:
|
||||
en: 1f62d5d938144666
|
||||
zh: dec0f30228c5b7a7
|
||||
en: 7386e88e7051dce7
|
||||
zh: dcea5b9e30374be3
|
||||
/agent-note-adjacent-agents-share-one-steer-send-message-operation/consequences:
|
||||
en: d55de74742c3af4b
|
||||
zh: d894f9a33c8e4691
|
||||
en: 22a3af4c51aee23e
|
||||
zh: 9d59315313d0f761
|
||||
+15
-16
@@ -4,26 +4,25 @@ Status: implemented
|
||||
|
||||
English | [中文](2026-08-27-adjacent-agent-steer-messaging.zh.md)
|
||||
|
||||
The adjacency, discovery, and parent-lifetime restrictions below are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md); the remaining decisions stay active. The tool identity marker and initial-task communication guidance below are superseded by [tool-owned communication](2026-10-22-subagent-tool-owned-communication.md).
|
||||
|
||||
## Problem
|
||||
|
||||
Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, source attribution, and scheduling.
|
||||
|
||||
A continuable child owns its own Session, so its parent does not automatically receive the child's transcript, tool output, or reasoning. The return path must therefore remain explicit and repeatable: a child may send progress before it finishes, remain available after sending, or fail before it can cooperate. Turning every final assistant message into an implicit result would conflate turn completion with model-selected communication and would not cover abnormal endings.
|
||||
A continuable child owns its own Session, so its parent does not automatically receive the child's transcript, tool output, or reasoning. Explicit messages let a child send actionable findings before finishing. The runtime separately delivers terminal output in a settlement notice when the direct creator is still online.
|
||||
|
||||
The child-only tool and system-prompt section also preceded every inherited fork turn. They made a continuable fork child's request head differ from its parent's before the history that fork exists to reuse, forcing the provider to prefill the entire copied transcript again.
|
||||
|
||||
## Decision
|
||||
|
||||
`SubagentRuntime.sendMessage(sender, targetId, content, { signal })` is the only public model-authored message operation. The continuation manager accepts only the exact live sender and a target on one adjacent edge:
|
||||
`SubagentRuntime.sendMessage(sender, targetId, content, { signal })` is the only public model-authored message operation. The continuation manager accepts only the exact live sender and a target in the same root tree; [tree-wide communication](2026-10-16-tree-wide-agent-communication.md) owns authorization and discovery.
|
||||
|
||||
- parent to direct continuable child, authorized by the child's durable `SessionHeader.parentSession`;
|
||||
- resident continuable child to its exact live direct parent, authorized by the child's Activation.
|
||||
Self-targets, stale Agent objects, unknown targets, and external executions are not alternate routes. The operation has no caller-supplied source, delivery mode, offline root mailbox, or provider dispatch.
|
||||
|
||||
Siblings, self-targets, ancestors beyond one edge, stale Agent objects, unknown targets, and one-shot children are not alternate routes. The operation has no caller-supplied source, delivery mode, offline parent mailbox, or provider dispatch.
|
||||
Every accepted message uses `Agent.steer()`. A running target receives it at the nearest step boundary; an idle target starts a turn. An absent target is cold-resumed under the root before the same Steer delivery; its creation ancestors can remain offline. The manager retains waking-send accounting so a continuation-managed target cannot settle between synchronous inbox insertion and driver admission.
|
||||
|
||||
Every accepted message uses `Agent.steer()`. A running target receives it at the nearest step boundary; an idle target starts a turn. An absent direct child is cold-resumed through the existing continuation lifecycle before the same Steer delivery. The manager retains waking-send accounting so a continuation-managed target cannot settle between synchronous inbox insertion and driver admission.
|
||||
|
||||
Every direction uses one durable source. The service derives `senderSessionId` from the authorized Agent and frames the model-visible content as `Agent <sender-id> sent a message:`, so attribution cannot diverge from authority.
|
||||
Every direction uses one durable source. The service derives `senderSessionId` from the exact live sender and frames content as `Agent <sender-id> sent a message:`.
|
||||
|
||||
```ts
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -41,20 +40,20 @@ The globally registered model tool is direction-neutral and has one fixed schema
|
||||
|
||||
```ts
|
||||
interface SendMessageInput {
|
||||
readonly agent_id: string
|
||||
readonly agent: string
|
||||
readonly message: string
|
||||
}
|
||||
```
|
||||
|
||||
Parents and children inherit the same definition in the same registry order. The standard definition carries a process-stable internal identity that a scoped same-name tool does not satisfy. An independently selected preset may omit the tool, a historical descriptor filter may remove it on resume, and a scoped replacement may provide different semantics; neither case receives the standard call instruction. When the standard tool remains visible, the continuation manager appends the JSON-encoded direct parent id and the instruction to send one self-contained result before finishing, plus earlier actionable findings, to the child's initial user task. For a fork child this task follows the inherited completed-turn prefix; no child-only system-prompt section or tool schema precedes that prefix.
|
||||
Parents and children inherit the same definition in the same registry order. The standard definition carries a process-stable internal identity that a scoped same-name tool does not satisfy. An independently selected preset may omit the tool, a historical descriptor filter may remove it on resume, and a scoped replacement may provide different semantics; none receives the standard call instruction. When the standard tool remains visible, the continuation manager appends the JSON-encoded direct parent id, automatic final-answer delivery conditions, and guidance to send earlier actionable findings to the child's initial user task. For a fork child this task follows the inherited completed-turn prefix; no child-only system-prompt section or tool schema precedes that prefix.
|
||||
|
||||
The instruction is guidance, not settlement enforcement. Sending does not end the child's turn, zero or several calls remain mechanically valid, and the runtime never rejects a child for staying silent. The manager-owned `subagent-settled` notice remains unconditional and separately attributed because it records how an Activation ended and preserves terminal output when the child cannot cooperate.
|
||||
The instruction is guidance, not settlement enforcement. Sending does not end the child's turn, zero or several calls remain mechanically valid, and the runtime never rejects a child for staying silent. The separately attributed `subagent-settled` notice records the Activation's outcome and terminal output only when its direct creator is still online and delivery is `parent`.
|
||||
|
||||
Human browser prompts are not model-authored Agent messages. The remote prompt path keeps a private Queue delivery so each human prompt remains a distinct turn. Interrupt behavior and settlement delivery remain independent.
|
||||
|
||||
### Complete removal and reintroduction condition
|
||||
|
||||
The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema, `tool:report` prompt section, `reportDelivery` configuration, report-specific message source, catalog entries, composition rows, and supported-behavior snapshots are absent. The unified tool gives up the recipient-free child shortcut and the old ability for a structural return tool to survive an explicit child allow-list. Those capabilities return only if a concrete use case requires semantics that an adjacent `agent_id` and fixed Steer cannot express; reintroducing them requires a distinct model operation and prefix-cost evidence, not an alias over `sendMessage()`.
|
||||
The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema, `tool:report` prompt section, `reportDelivery` configuration, report-specific message source, catalog entries, composition rows, and supported-behavior snapshots are absent. The unified tool gives up the recipient-free child shortcut and the old ability for a structural return tool to survive an explicit child allow-list. Those capabilities return only if a concrete use case requires semantics that a tree member’s `agent` reference and fixed Steer cannot express; reintroducing them requires a distinct model operation and prefix-cost evidence, not an alias over `sendMessage()`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -64,16 +63,16 @@ The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema,
|
||||
|
||||
**Make `report` global.** Roots, one-shot children, remote children, and agentless callers cannot derive a report recipient. Advertising it globally would make schema visibility disagree with authority, while `send_message` already makes the recipient explicit.
|
||||
|
||||
**Turn every child final message into an implicit send.** A long-lived child may have nothing useful to send in one turn and several findings in another. Automatic delivery would merge model-authored communication with the runtime's settlement account and could not replace the unconditional notice on errors, cancellation, or token exhaustion.
|
||||
**Treat the final answer as a model-authored send.** A settlement notice must preserve terminal output and abnormal endings even when the child cannot cooperate. It keeps runtime attribution distinct from findings the child explicitly chose to send.
|
||||
|
||||
**Rely only on the tool description.** A tool description helps after the model considers that tool; the failure mode is a child that believes it is finished without considering any return call. Initial-task guidance reaches that decision without changing the inherited system or tool prefix.
|
||||
**Rely only on the tool description.** A tool description helps after the model considers that tool; initial-task guidance also explains which earlier findings should reach the parent and what the final answer must contain, without changing the inherited system or tool prefix.
|
||||
|
||||
**Keep quiet delivery as deployment policy.** A quiet model-authored message can be accepted while an idle target never reads it. Fixed Steer gives both directions one delivery meaning and preserves accepted order with later settlement notices.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Model consumers expose one `send_message({ agent_id, message })` definition to parents and children, with no model-selected Queue versus Steer parameter.
|
||||
- The continuation manager remains the sole owner of adjacency authorization, residency, cold resume, waking admission, and teardown races.
|
||||
- Model consumers expose one `send_message({ agent, message })` definition to parents and children, with no model-selected Queue versus Steer parameter.
|
||||
- The continuation manager remains the sole owner of tree authorization, residency, cold resume, waking admission, and teardown races.
|
||||
- Accepted messages may extend a running target's current turn; messages waiting together share next-step FIFO ordering.
|
||||
- Caller cancellation owns work only until inbox acceptance and does not retract an accepted message or dispose the target.
|
||||
- The initial task carries JSON-encoded dynamic parent addressing after a fork prefix, while the request-head system prompt and tool ordering remain reusable.
|
||||
|
||||
+15
-16
@@ -4,26 +4,25 @@ Status: implemented
|
||||
|
||||
[English](2026-08-27-adjacent-agent-steer-messaging.md) | 中文
|
||||
|
||||
邻接消息、发现范围和父子生命周期限制已由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)替代;其余决策仍然有效。 下文的工具身份标记和初始任务通信指导由[工具拥有通信指导](2026-10-22-subagent-tool-owned-communication.zh.md)取代。
|
||||
|
||||
## 问题
|
||||
|
||||
可继续 Agent 最初使用方向专属的模型控制。parent 调用 `send_message({ subagent_id, message })`,委托给 FIFO `followup` 服务操作。child 则获得 child 作用域的 `report({ output })` 工具、`tool:report` 系统提示词 section,以及由部署选择的静默或唤醒投递。两个工具用不同 schema、服务路径、来源与调度描述同一个相邻 Agent 操作。
|
||||
|
||||
可继续 child 拥有自己的 Session,因此 parent 不会自动收到 child 的 transcript(文本记录)、工具输出或推理。返回路径必须保持显式且可重复:child 可以在结束前发送进度、发送后仍保持可用,也可能在来得及配合前失败。把每条最终 assistant 消息变成隐式结果会混淆轮次完成与模型选择的通信,而且无法覆盖异常结束。
|
||||
可继续 child 拥有自己的 Session,因此 parent 不会自动收到 child 的 transcript(文本记录)、工具输出或推理。显式消息让 child 在结束前发送可操作的发现。直接创建者仍在线时,runtime 另行通过结算通知投递终态输出。
|
||||
|
||||
child 专属工具与系统提示词 section 还位于每个继承 fork 轮次之前。它们让可继续 fork child 的请求头在 fork 旨在复用的历史之前就与 parent 不同,迫使提供方重新预填充整份复制 transcript。
|
||||
|
||||
## 决策
|
||||
|
||||
`SubagentRuntime.sendMessage(sender, targetId, content, { signal })` 是唯一公开的模型编写消息操作。继续执行管理器只接受确切在线 sender 与一条相邻边上的目标:
|
||||
`SubagentRuntime.sendMessage(sender, targetId, content, { signal })` 是唯一公开的模型编写消息操作。继续执行管理器只接受确切在线 sender 与同一根树中的目标;[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)负责授权与发现规则。
|
||||
|
||||
- parent 到直接可继续 child,由 child 的持久化 `SessionHeader.parentSession` 授权;
|
||||
- 驻留的可继续 child 到其确切在线直接 parent,由 child 的 Activation 授权。
|
||||
自身目标、过期 Agent 对象、未知目标和外部执行不是替代路由。操作不接受调用方提供的 source、投递模式、离线根邮箱或提供方分发。
|
||||
|
||||
sibling、自身目标、超过一条边的 ancestor、陈旧 Agent 对象、未知目标与一次性 child 都不是替代路由。该操作没有调用方提供的 source、投递模式、离线 parent mailbox 或提供方分发。
|
||||
每条被接受的消息都使用 `Agent.steer()`。运行中目标在最近 step 边界接收消息;空闲目标启动轮次。不在线目标先在根下冷恢复,再接受同一 Steer 投递;其创建祖先可以保持离线。管理器保留唤醒发送记账,因此受继续执行管理的目标不会在同步 inbox 插入与 driver 准入之间结算。
|
||||
|
||||
每条被接受的消息都使用 `Agent.steer()`。运行中目标在最近 step 边界接收消息;空闲目标启动轮次。缺失的直接 child 会先通过现有继续执行生命周期冷恢复,再接受同一 Steer 投递。管理器保留唤醒发送记账,因此受继续执行管理的目标不会在同步 inbox 插入与 driver 准入之间结算。
|
||||
|
||||
两个方向使用同一种持久来源。服务从已授权 Agent 推导 `senderSessionId`,并把模型可见内容组装为 `Agent <sender-id> sent a message:`,因此来源信息不会偏离权限。
|
||||
所有方向使用同一种持久 source。服务从确切在线发送方推导 `senderSessionId`,并以 `Agent <sender-id> sent a message:` 包装内容。
|
||||
|
||||
```ts
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -41,20 +40,20 @@ interface AgentMessageSource {
|
||||
|
||||
```ts
|
||||
interface SendMessageInput {
|
||||
readonly agent_id: string
|
||||
readonly agent: string
|
||||
readonly message: string
|
||||
}
|
||||
```
|
||||
|
||||
parent 与 child 以相同注册表顺序继承相同定义。标准定义携带进程稳定的内部身份,同名的作用域工具不满足该身份。独立选择的 preset 可以省略该工具,历史 descriptor 过滤器可在恢复时移除该工具,作用域替代工具也可以提供不同语义;两种情况都不会收到标准调用指令。当标准工具仍可见时,继续执行管理器会把经过 JSON 编码的直接 parent id、结束前发送一份自包含结果的指令,以及更早发送可操作发现的指令追加到 child 初始用户任务。对 fork child 而言,该任务位于继承的已完成轮次前缀之后;没有 child 专属系统提示词 section 或工具 schema 位于此前缀之前。
|
||||
parent 与 child 以相同注册表顺序继承相同定义。标准定义携带进程稳定的内部身份,同名的作用域工具不满足该身份。独立选择的 preset 可以省略该工具,历史 descriptor 过滤器可在恢复时移除该工具,作用域替代工具也可以提供不同语义;这些情况都不会收到标准调用指令。当标准工具仍可见时,继续执行管理器会把经过 JSON 编码的直接 parent id、最终回答的自动投递条件,以及提前发送可操作发现的指导追加到 child 初始用户任务。对 fork child 而言,该任务位于继承的已完成轮次前缀之后;没有 child 专属系统提示词 section 或工具 schema 位于此前缀之前。
|
||||
|
||||
该指令是指导,不是结算强制。发送不会结束 child 轮次,机制仍允许零次或多次调用,runtime 绝不会因 child 保持沉默而拒绝它。由管理器负责的 `subagent-settled` 通知仍无条件发送并采用独立来源,因为它记录 Activation 如何结束,并在 child 无法配合时保留终态输出。
|
||||
该指令是指导,不是结算强制。发送不会结束 child 轮次,机制仍允许零次或多次调用,runtime 绝不会因 child 保持沉默而拒绝它。采用独立来源的 `subagent-settled` 通知仅在直接创建者仍在线且投递方式为 `parent` 时,记录 Activation 的结果与终态输出。
|
||||
|
||||
浏览器中的人类提示不是模型编写的 Agent 消息。远程提示路径保留私有 Queue 投递,使每条人类提示保持为独立轮次。中断行为与结算投递保持独立。
|
||||
|
||||
### 完整移除与重新引入条件
|
||||
|
||||
独立的 `@deepseek-ai/dsh-tool-subagent-report` 包、`report` schema、`tool:report` 提示词 section、`reportDelivery` 配置、report 专属消息来源、目录项、组合行和受支持行为快照均已不存在。统一工具放弃了无需接收方的 child 快捷方式,也放弃了让结构性返回工具绕过显式 child allow-list 的旧能力。只有具体用例需要相邻 `agent_id` 与固定 Steer 无法表达的语义时,这些能力才会重新出现;重新引入需要独立的模型操作与前缀成本证据,而非 `sendMessage()` 之上的别名。
|
||||
独立的 `@deepseek-ai/dsh-tool-subagent-report` 包、`report` schema、`tool:report` 提示词 section、`reportDelivery` 配置、report 专属消息来源、目录项、组合行和受支持行为快照均已不存在。统一工具放弃了无需接收方的 child 快捷方式,也放弃了让结构性返回工具绕过显式 child allow-list 的旧能力。只有具体用例需要树成员的 `agent` 引用与固定 Steer 无法表达的语义时,这些能力才会重新出现;重新引入需要独立的模型操作与前缀成本证据,而非 `sendMessage()` 之上的别名。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -64,16 +63,16 @@ parent 与 child 以相同注册表顺序继承相同定义。标准定义携带
|
||||
|
||||
**让 `report` 全局可见。** 根 Agent、一次性 child、远程 child 与无 Agent 调用方无法推导 report 接收方。全局宣传它会让 schema 可见性与权限不一致,而 `send_message` 已显式给出接收方。
|
||||
|
||||
**把每条 child 最终消息变成隐式发送。** 长期运行的 child 可能在某个轮次没有值得发送的内容,在另一个轮次却有多条发现。自动投递会混合模型编写通信与 runtime 结算说明,而且无法替代错误、取消或 token 耗尽时的无条件通知。
|
||||
**将最终回答当作模型编写的发送。** 即使 child 无法配合,结算通知仍须保留终态输出与异常结束信息。它将 runtime 来源与 child 显式选择发送的发现区分开。
|
||||
|
||||
**只依赖工具描述。** 工具描述会在模型考虑该工具后提供帮助;失败模式是 child 认为自己已经完成而根本没有考虑返回调用。初始任务指导能触及该决策,又不会改变继承的系统或工具前缀。
|
||||
**只依赖工具描述。** 工具描述会在模型考虑该工具后提供帮助;初始任务指导还说明哪些早期发现应发送给 parent,以及最终回答须包含什么,又不会改变继承的系统或工具前缀。
|
||||
|
||||
**保留静默投递作为部署策略。** 静默的模型编写消息可能被接受,但空闲目标永远不会读取它。固定 Steer 为两个方向提供一种投递含义,并保持与后续结算通知的接受顺序。
|
||||
|
||||
## 后果
|
||||
|
||||
- 模型 Consumer 向 parent 与 child 公开一个 `send_message({ agent_id, message })` 定义,不提供模型选择的 Queue 与 Steer 参数。
|
||||
- 继续执行管理器仍是相邻关系授权、驻留、冷恢复、唤醒准入与拆卸竞态的唯一所有者。
|
||||
- 模型 Consumer 向 parent 与 child 公开一个 `send_message({ agent, message })` 定义,不提供模型选择的 Queue 与 Steer 参数。
|
||||
- 继续执行管理器仍是整树授权、驻留、冷恢复、唤醒准入与拆卸竞态的唯一所有者。
|
||||
- 被接受的消息可以延长运行中目标的当前轮次;一起等待的消息共享 next-step FIFO 顺序。
|
||||
- 调用方取消只在 inbox 接受前掌管工作,不会撤回已接受消息或 dispose(资源释放)目标。
|
||||
- 初始任务在 fork 前缀之后携带经过 JSON 编码的动态 parent 地址,而请求头系统提示词与工具顺序保持可复用。
|
||||
|
||||
+8
-8
@@ -9,14 +9,14 @@
|
||||
en: 98b49b900b8d2f84
|
||||
zh: 557faac2976592be
|
||||
/agent-note-explicit-agent-identity-at-runtime-boundaries/decision:
|
||||
en: 1c628cf9c743c639
|
||||
zh: d969b2475fe4a155
|
||||
en: 4a3ab5108f37520f
|
||||
zh: d13fcd448101a95d
|
||||
/agent-note-explicit-agent-identity-at-runtime-boundaries/verification:
|
||||
en: 85130faaa171036b
|
||||
zh: 06e5e9fab32dbfb4
|
||||
en: ce1733edf03f0387
|
||||
zh: 8e1ca2b69e81968e
|
||||
/agent-note-explicit-agent-identity-at-runtime-boundaries/alternatives-considered:
|
||||
en: 2c41456509d54d21
|
||||
zh: 5704ee9a2274e300
|
||||
en: 34f5f8571b21897c
|
||||
zh: cf16094bc0027ca1
|
||||
/agent-note-explicit-agent-identity-at-runtime-boundaries/consequences:
|
||||
en: 08ab86b89fc3ec6f
|
||||
zh: 3866e6557858543a
|
||||
en: 0b5914067f14282e
|
||||
zh: c41d21aeb28e357f
|
||||
+8
-8
@@ -14,34 +14,34 @@ Without an explicit owner, `SubagentManager` creates and resumes children throug
|
||||
|
||||
## Decision
|
||||
|
||||
Runtime interfaces carry Agent identity at the point that owns it. `AgentSetup` receives `(agentCtx, agent)`; Agent creation and resume options carry `parentAgent` for a runtime child; scoped events carry their Agent in the payload; Remote forwarding verifies that `request.agent` is the carrier key; and Host Typert Context resolution maps wire identity to a live Agent Context without a reverse scan. `agent.ctx` remains the registration and lifecycle owner and exposes no reverse Agent property.
|
||||
Runtime interfaces carry Agent identity at the point that owns it. `AgentSetup` receives `(agentCtx, agent)`; each subagent Activation retains its exact runtime root; scoped events carry their Agent in the payload; Remote forwarding verifies that `request.agent` is the carrier key; and Host Typert Context resolution maps wire identity to a live Agent Context without a reverse scan. `agent.ctx` remains the registration and lifecycle owner and exposes no reverse Agent property.
|
||||
|
||||
Scope-aware registries continue to use the opaque scope key only for registration membership. Tool-subagent does not classify that key or resolve an Agent from Context. A direct `AgentSetup` passes the unpublished Session explicitly and installs through the supplied Context before publication. For a settings-backed standing preset, the event payload supplies the Agent, its Session supplies the policy target, and its Context owns the registrations.
|
||||
Scope-aware registries continue to use the opaque scope key only for registration membership. Tool-subagent does not classify that key or resolve an Agent from Context. A direct `AgentSetup` receives the Agent explicitly and installs through the supplied Context before Agent publication. For a settings-backed standing preset, the event payload supplies the Agent, its Session supplies the policy target, and its Context owns the registrations.
|
||||
|
||||
`SubagentManager` puts the exact parent in both fresh-creation and cold-resume options. A live continuable child is therefore excluded from `AgentRegistry.roots()` and satisfies `isOwnedBy(child.id, parent)`. Durable `parentSession` metadata does not substitute for this relation: a fork or resumed Session may be a runtime root when no live Agent owns it.
|
||||
`SubagentManager.rootOf(agent)` reads runtime membership from existing Activations; both the child and root must match their live registry objects. Ordinary Sessions resolve to themselves, and unmanaged subagent headers grant no membership. Durable `parentSession` describes creation edges; an Activation retains the exact root while intermediate creators are offline, and same-id replacements cannot inherit it. Root-only consumers classify the explicit subagent origin.
|
||||
|
||||
The [scope reference](../../../../packages/core/scope/README.md) and [Agent loop reference](../../../../packages/core/agent-loop/README.md) document registration and lifecycle behavior. The [initiator-scope decision](2026-07-15-agent-initiator-scope.md) owns private-chain rationale; explicit runtime ownership does not change causal initiation or registration lifetime.
|
||||
|
||||
## Verification
|
||||
|
||||
Agent creation tests pin explicit root and child ownership. Continuation integration tests keep a real child live long enough to assert both `roots()` exclusion and `isOwnedBy()` membership. Existing Schedule tests verify that root-only registrations stay absent from an explicitly owned child.
|
||||
Managed-child tests verify intermediate-creator disposal and exact-instance checks after child or root replacement. SDK approval tests verify routing during a turn after the direct creator exits. Schedule and user-question tests reject delegated Sessions while ordinary historical forks remain independent.
|
||||
|
||||
Remote-event tests reject a missing or mismatched Agent before forwarding a scoped waterfall. Tool-subagent tests verify that direct setup installs before Session publication; standing-preset tests verify per-Session policy sampling and inheritance.
|
||||
Remote-event tests reject a missing or mismatched Agent before forwarding a scoped waterfall. Tool-subagent tests verify that direct setup installs before Agent publication; standing-preset tests verify per-Session policy sampling and inheritance.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep `Context.agent`.** A reverse accessor makes registration ownership look like operation identity and requires every Context derivation, adapter, and test double to preserve an association unrelated to Cordis service selection or effect cleanup.
|
||||
|
||||
**Infer runtime ownership from the caller Context.** A private manager Context, an Agent Context, and a standing preset Context can all call the same factory. Context ancestry therefore does not state which live Agent owns the result; the creator must put the parent it already knows in the request options.
|
||||
**Infer runtime ownership from the caller Context.** A private manager Context, an Agent Context, and a standing preset Context can all call the same factory. Context ancestry therefore does not state which live Agent owns the result; the subagent Activation retains the exact root supplied at admission.
|
||||
|
||||
**Classify Agent scope keys.** An opaque scope key states routing membership, not domain identity. Classifying it would make Agent the center of composition and would still couple a plugin's effect owner to the Session whose policy it needs.
|
||||
|
||||
**Use the initiating Agent as creation ownership.** Initiator scope records causal asynchronous execution, not lifetime ownership. A parent may initiate work that intentionally creates a root, and setup remains outside the child's driver boundary.
|
||||
|
||||
**Use durable Session lineage.** `parentSession` records conversation ancestry across process lifetimes. Runtime ownership controls live roots and teardown, so equating the two would prevent a legitimately resumed fork from becoming a top-level Agent.
|
||||
**Use durable Session lineage.** `parentSession` records creation ancestry across process lifetimes. An intermediate creator can be offline while its descendant remains managed by a live root; durable ids also cannot distinguish replaced Agent instances. Historical ordinary forks may retain parent metadata without being delegated.
|
||||
|
||||
## Consequences
|
||||
|
||||
Lifecycle options, events, service requests, and transport requests carry explicit Agent identities, so each operation states the identity it uses and TypeScript checks both sides. Context remains reusable for dependency access and effect ownership without becoming an alternate domain-object locator.
|
||||
|
||||
Continuable children have the same runtime parent relation as one-shot in-process children. Root-only consumers exclude them, parent teardown can reason from one live ownership graph, and durable lineage remains free to describe history rather than process-local lifetime.
|
||||
Continuable children have one runtime root independent of creator residency. The subagent manager owns tree teardown and local membership; the core factory and registry retain only instance creation, publication, and disposal. Third-party callers create managed children through the subagent service rather than assigning ownership to arbitrary factory-created Agents.
|
||||
+8
-8
@@ -14,34 +14,34 @@ Agent 的 Cordis Context 拥有注册及其清理。Agent 身份则为某项操
|
||||
|
||||
## 决策
|
||||
|
||||
运行时接口在拥有身份的位置携带 Agent 身份。`AgentSetup` 接收 `(agentCtx, agent)`;创建与恢复 Agent 的 options 通过 `parentAgent` 标识运行时子级;作用域事件在 payload 中携带 Agent;Remote 转发校验 `request.agent` 就是 carrier key;Host Typert Context 解析则把协议身份映射到存活 Agent Context,不执行反向扫描。`agent.ctx` 继续拥有注册和生命周期,不暴露反向 Agent 属性。
|
||||
运行时接口在拥有身份的位置携带 Agent 身份。`AgentSetup` 接收 `(agentCtx, agent)`;每个 subagent Activation 保留其确切运行时树根;作用域事件在 payload 中携带 Agent;Remote 转发校验 `request.agent` 就是 carrier key;Host Typert Context 解析则把协议身份映射到存活 Agent Context,不执行反向扫描。`agent.ctx` 继续拥有注册和生命周期,不暴露反向 Agent 属性。
|
||||
|
||||
感知作用域的注册表继续仅使用不透明作用域键判断注册成员关系。tool-subagent 不会分类该键,也不会从 Context 解析 Agent。直接 `AgentSetup` 显式传入尚未发布的 Session,并在发布前通过所给 Context 完成安装。对于由设置控制的常驻 preset,事件 payload 提供 Agent,其 Session 提供策略目标,其 Context 拥有注册项。
|
||||
感知作用域的注册表继续仅使用不透明作用域键判断注册成员关系。tool-subagent 不会分类该键,也不会从 Context 解析 Agent。直接 `AgentSetup` 显式接收 Agent,并在 Agent 发布前通过所给 Context 完成安装。对于由设置控制的常驻 preset,事件 payload 提供 Agent,其 Session 提供策略目标,其 Context 拥有注册项。
|
||||
|
||||
`SubagentManager` 会把确切父级放进全新创建与冷恢复的 options。因此,存活的可续跑子级不会出现在 `AgentRegistry.roots()` 中,并且满足 `isOwnedBy(child.id, parent)`。持久化 `parentSession` 元数据不能代替这项关系:没有存活 Agent 拥有 fork 或已恢复会话时,它仍可成为 runtime root。
|
||||
`SubagentManager.rootOf(agent)` 从已有 Activation 读取运行时成员关系;子级和树根都必须匹配注册表中的存活对象。普通 Session 返回自身,非托管的 subagent header 不授予成员身份。持久化 `parentSession` 描述创建关系;中间创建者离线时,Activation 保留确切树根,同 id 替代实例不能继承它。仅限根级的消费方依据显式 subagent 来源分类。
|
||||
|
||||
[作用域参考](../../../../packages/core/scope/README.zh.md)与 [Agent loop 参考](../../../../packages/core/agent-loop/README.zh.md)说明注册和生命周期行为。[发起方作用域决策](2026-07-15-agent-initiator-scope.zh.md)负责私有调用链理由;显式运行时归属不改变因果发起关系或注册生命周期。
|
||||
|
||||
## 验证
|
||||
|
||||
Agent 创建测试锁定显式的根级与子级归属。continuation 集成测试让一个真实子级保持存活,直到断言其既不属于 `roots()`、又满足 `isOwnedBy()`。现有 Schedule 测试验证仅限根级的注册项不会出现在显式归属的子级中。
|
||||
托管子级测试验证中间创建者释放,以及子级或树根替换后的确切实例检查。SDK 审批测试验证直接创建者退出后的轮次内路由。Schedule 与用户问题测试拒绝委派 Session,历史普通 fork 则保持独立。
|
||||
|
||||
Remote 事件测试会在转发作用域 waterfall 前拒绝缺失或不匹配的 Agent。tool-subagent 测试验证 direct setup 会在 Session 发布前完成安装;常驻 preset 测试验证逐 Session 的策略读取与继承。
|
||||
Remote 事件测试会在转发作用域 waterfall 前拒绝缺失或不匹配的 Agent。tool-subagent 测试验证 direct setup 会在 Agent 发布前完成安装;常驻 preset 测试验证逐 Session 的策略读取与继承。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留 `Context.agent`。** 反向 accessor 会让注册所有权看起来等同于操作身份,还要求每个 Context 派生、适配器和测试替身保留一项与 Cordis 服务选择或 effect 清理无关的关联。
|
||||
|
||||
**从调用方 Context 推断运行时归属。** 私有管理器 Context、Agent Context 和常驻 preset Context 都能调用同一个工厂。因此,Context 祖先关系无法说明由哪个存活 Agent 拥有结果;创建方必须把它已知的父级放进请求 options。
|
||||
**从调用方 Context 推断运行时归属。** 私有管理器 Context、Agent Context 和常驻 preset Context 都能调用同一个工厂。因此,Context 祖先关系无法说明由哪个存活 Agent 拥有结果;subagent Activation 保留准入时传入的确切树根。
|
||||
|
||||
**分类 Agent 作用域键。** 不透明作用域键表达路由成员关系,而不是领域身份。分类该键会让 Agent 成为组合中心,也仍会把插件的 effect 所有者与策略所需的 Session 耦合起来。
|
||||
|
||||
**使用发起 Agent 作为创建归属。** 发起方作用域记录异步执行的因果关系,而非生命周期归属。父级可能发起有意创建根级 Agent 的工作,而 setup 仍位于子级驱动边界之外。
|
||||
|
||||
**使用持久化会话谱系。** `parentSession` 跨进程生命周期记录对话祖先关系。运行时归属控制存活根级和 teardown,因此把二者等同会阻止合法恢复的 fork 成为顶层 Agent。
|
||||
**使用持久化会话谱系。** `parentSession` 跨进程生命周期记录创建祖先关系。中间创建者可以离线,而后代仍由存活树根管理;持久 id 也无法区分替换后的 Agent 实例。历史普通 fork 可能保留父级元数据,但并不属于委派。
|
||||
|
||||
## 后果
|
||||
|
||||
生命周期 options、事件、服务请求和传输请求会携带显式 Agent 身份,因此每项操作都会声明自身使用的身份,TypeScript 也会检查两侧。Context 可以继续复用于依赖访问与 effect 所有权,而不会成为另一种领域对象定位器。
|
||||
|
||||
可续跑子级与一次性进程内子级使用同一种运行时父级关系。仅限根级的消费方会排除这些子级,父级 teardown 可以依据唯一的存活归属图推理,而持久化谱系仍可描述历史,不必承担进程内生命周期语义。
|
||||
可续跑子级拥有独立于创建者驻留状态的唯一运行时树根。subagent 管理器拥有整树 teardown 和本地成员关系;核心工厂与注册表仅保留实例创建、发布和释放。第三方调用方通过 subagent 服务创建托管子级,而非给任意工厂创建的 Agent 指定归属。
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 5a991fec31578cea
|
||||
zh: 13b75aa36eb22907
|
||||
/agent-note-parent-owned-subagent-catalog-events/decision:
|
||||
en: 4319c502038ab34d
|
||||
zh: b11825ff9d744822
|
||||
en: d2290d8e70d42054
|
||||
zh: 64a2d1d0915f6c3e
|
||||
/agent-note-parent-owned-subagent-catalog-events/alternatives-considered:
|
||||
en: 7a3d4b9f69c243d8
|
||||
zh: 82b03d16ccc94dd2
|
||||
|
||||
@@ -14,7 +14,7 @@ The child descriptor remains necessary for recovery and composition, but it cann
|
||||
|
||||
The shared activation entry point, external execution ownership, and caller-versus-parent result delivery are governed by [Unified subagent activations](../simplification/2026-09-17-unified-subagent-activations.md). This record retains the independent rationale described below.
|
||||
|
||||
The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Every local child enters the catalog independently of its result recipient; external children enter only with parent delivery. Workflow membership records describe execution progress and do not replace local Session discovery. Each event contains `childId`, `childCreatedAt`, mode, and the mode-discriminated label. Local children use `continuable`; external executions use `external` and cannot be opened as local Sessions. Historical `one-shot` and `unknown` entries remain readable. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog.
|
||||
The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Every local child enters the catalog independently of its result recipient; external children enter only with parent delivery. Workflow membership records describe execution progress and do not replace local Session discovery. Each event contains `childId`, `childCreatedAt`, mode, the mode-discriminated label, and, from payload v3, the child's [name](2026-11-02-subagent-catalog-names.md). Local children use `continuable`; external executions use `external` and cannot be opened as local Sessions. Historical `one-shot` and `unknown` entries remain readable. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog.
|
||||
|
||||
Creation publishes only successful facts. A local activation admits the initial prompt before appending its catalog event. An external activation appends its event after provider startup and before returning the receipt. Failed admission or publication releases the unpublished execution; there is no compensating catalog event or rollback protocol.
|
||||
|
||||
@@ -22,7 +22,7 @@ The child header and `subagent/descriptor` remain authoritative for recovery and
|
||||
|
||||
The registered `subagentCatalog` projection materializes the parent facts. It delegates storage, append, iteration, and checkpoint validation to [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.md), which stores facts in a persistent stack of 64-entry chunks, so an append copies at most the head chunk in bounded O(1) work. Materialization visits chunks from oldest to newest and preserves parent catalog event order in O(D) time for D facts. Concurrent creation is ordered by successful catalog append, independent of child timestamps and ids. A projection checkpoint clones the state once in O(D); projection-cache writes remain asynchronous and use the existing mandatory creation, turn-end, and disposal points.
|
||||
|
||||
The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 6 accepts external membership, so the projection registry rebuilds incompatible caches from Session events. Payload v0 and v1 remain unchanged; v2 records external executions.
|
||||
The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 7 accepts external membership and child names, so the projection registry rebuilds incompatible caches from Session events. Payload v0 and v1 remain unchanged; v2 records external executions; v3 adds names to continuable and external entries.
|
||||
|
||||
Fork isolation uses the exact `Session.inheritedEventCount` supplied to projection initialization. The fold ignores `subagent/catalog` events below that offset. The state stores the inherited offset but not each event seq because acceptance is decided during folding.
|
||||
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ child descriptor 对恢复与 composition 仍然必要,但它不能作为 disc
|
||||
|
||||
共享 activation 入口、外部执行所有权,以及调用方与父级之间的结果投递选择由[统一 subagent activation](../simplification/2026-09-17-unified-subagent-activations.zh.md) 决策拥有。本记录保留下述独立理由。
|
||||
|
||||
父级 Session 的必读 `subagent/catalog` 事件是发现直接子级的持久化权威。每个本地子级都会进入目录,与结果接收方无关;外部子级仅在 parent 投递时进入目录。工作流成员记录描述执行进度,不替代本地 Session 发现。每个事件包含 `childId`、`childCreatedAt`、mode 与按 mode 区分的 label。本地子级使用 `continuable`;外部执行使用 `external`,不能作为本地 Session 打开。历史 `one-shot` 与 `unknown` 条目仍可读取。无效的自身事实(包括不支持的载荷版本)会使投影恢复失败,因为静默丢弃必读事实将返回不完整的 catalog。
|
||||
父级 Session 的必读 `subagent/catalog` 事件是发现直接子级的持久化权威。每个本地子级都会进入目录,与结果接收方无关;外部子级仅在 parent 投递时进入目录。工作流成员记录描述执行进度,不替代本地 Session 发现。每个事件包含 `childId`、`childCreatedAt`、mode、按 mode 区分的 label,以及从载荷 v3 起的子级[名称](2026-11-02-subagent-catalog-names.zh.md)。本地子级使用 `continuable`;外部执行使用 `external`,不能作为本地 Session 打开。历史 `one-shot` 与 `unknown` 条目仍可读取。无效的自身事实(包括不支持的载荷版本)会使投影恢复失败,因为静默丢弃必读事实将返回不完整的 catalog。
|
||||
|
||||
创建只发布成功事实。本地 activation 先接纳初始提示词,再追加 catalog 事件。外部 activation 在提供方启动后、返回回执前追加事件。准入或发布失败会释放未发布的执行;不存在补偿 catalog 事件或回滚协议。
|
||||
|
||||
@@ -22,7 +22,7 @@ child header 与 `subagent/descriptor` 继续拥有恢复与 composition 权威
|
||||
|
||||
注册的 `subagentCatalog` projection 物化 parent fact。它将存储、追加、迭代和检查点校验交给 [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.zh.md),后者以每块 64 项的持久 stack 保存事实,因此 append 最多复制 head chunk,以有界 O(1) 工作完成。materialization 从旧到新访问 chunk,对 D 条事实以 O(D) 时间保留父目录事件顺序。并发创建按目录成功追加的顺序排列,与 child 时间戳和 id 无关。projection checkpoint 以 O(D) 克隆 state;projection-cache 继续异步写入,并使用既有创建、turn-end 与 disposal 强制点。
|
||||
|
||||
工具库拥有 chunk 布局与共享容量常量;catalog 拥有事件校验、fork 过滤和行转换。catalog 投影状态版本 6 接受外部成员,因此投影注册表会从 Session 事件重建不兼容缓存。载荷 v0 与 v1 保持不变;v2 记录外部执行。
|
||||
工具库拥有 chunk 布局与共享容量常量;catalog 拥有事件校验、fork 过滤和行转换。catalog 投影状态版本 7 接受外部成员和子级名称,因此投影注册表会从 Session 事件重建不兼容缓存。载荷 v0 与 v1 保持不变;v2 记录外部执行;v3 为可继续和外部条目增加名称。
|
||||
|
||||
fork 隔离使用 projection 初始化时提供的精确 `Session.inheritedEventCount`。fold 忽略该 offset 之前的 `subagent/catalog` 事件。state 保存 inherited offset,但不保存每条 event seq,因为接受判定已在 fold 时完成。
|
||||
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@
|
||||
en: 4548e7fbdbee5b5a
|
||||
zh: 0078762ea92fa180
|
||||
/agent-note-consumer-owned-startup-strictness/decision:
|
||||
en: 1f11e04162e19ad1
|
||||
zh: d102e300eba9a2ea
|
||||
en: 62e93742af5bed66
|
||||
zh: 3944d685b6503a2c
|
||||
/agent-note-consumer-owned-startup-strictness/alternatives-considered:
|
||||
en: 1b67e062213184cd
|
||||
zh: 91cc8a015de2bfe4
|
||||
@@ -18,5 +18,5 @@
|
||||
en: 283dec1b336c540d
|
||||
zh: 2ed358079072cfff
|
||||
/agent-note-consumer-owned-startup-strictness/testing:
|
||||
en: c38221069ce98fd3
|
||||
zh: 9a2f91f539eba4d4
|
||||
en: cee14c22297e5471
|
||||
zh: a68748cc36f4a574
|
||||
@@ -20,6 +20,8 @@ The audit runs only during initial application boot. Later config HMR remains be
|
||||
|
||||
This policy governs [Web host boot](../../../../packages/boot/app-boot/README.md), including its [client plugin roster](2026-07-23-client-plugin-loading-model.md). [Per-session presets](2026-09-18-declarative-agent-presets.md) own a separate strict subtree audit.
|
||||
|
||||
The browser boot audit applies the same policy to the client plugin tree with its own global list in [`boot-client.ts`](../../../../packages/client/web/src/boot-client.ts): the module system, Session control, the renderer, the application frame, the Session view, and its sidebar. Browser entries are named by package, so the list holds package names; a name absent from the boot graph has no effect, and neither list is derived from the other. The list is the minimum fatal set: an entry that a listed entry waits on blocks startup too. Feature plugins, including credential masking, stay optional. A failed entry reports its activation error. Optional failures appear in one console warning, and in Settings → Plugins → Plugin list only after a later graph synchronization reconciles them. Renaming a listed package requires updating the list and its tests.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Add transactional and best-effort modes to vendored Loader.** Rejected because strictness belongs to the application or resource owner, while a Loader group contains unrelated plugins. A mode would also expand the vendor patch and leave callers to select a policy at every group.
|
||||
@@ -36,6 +38,8 @@ The compact terminal report keeps the failing plugins visible; a separate file r
|
||||
|
||||
App-boot unit tests cover absent and disabled required ids, optional import failure, config evaluation failure, synchronous and asynchronous `apply()` failure, pending dependencies, and required failure teardown. Unit expectations pin diagnostic grouping, preservation of original error objects and import logs, exporter cleanup, complete diagnostic values, private file creation, concurrent report names, and failed-write fallback. The built Web-profile acceptance asserts a single port-conflict stack without Node wrapper output, verifies the saved diagnostic file and its stderr fallback, serves the full UI with optional failures and exits nonzero without readiness when the required HTTP port is occupied or `modules` or `connection` cannot activate.
|
||||
|
||||
Browser audit unit tests (`boot-client.client.spec.ts`, `boot.client.spec.ts`) cover required and optional import, creation, activation, and pending failures, carrier presentation, and required names absent from the boot graph. Test carriers are stricter than the product: `TestClient` requires every roster entry to activate unless a case passes `allowInactiveEntries`, and the browser e2e lanes treat a `web boot: warning` as a failure with no opt-out.
|
||||
|
||||
The [Web process matrix](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts) independently exercises optional and required failures at startup and after native patch-file edits. Authenticated HTTP requests and plugin lifecycle files distinguish a usable application from a surviving process. These keyless process checks complement the [controlled-delivery unit tests](../../../../packages/boot/app-boot/tests/user-patches.spec.ts): unit tests isolate reconciliation failures, while the process tests also require the shipped launcher, native watcher, and bounded shutdown to work together.
|
||||
|
||||
The matrix enables Chokidar's `awaitWriteFinish` to acknowledge stable file contents before each reload; otherwise its short change-event suppression window can discard the next test edit. Native events remain required, and assertions wait for observed activation or failure rather than a fixed settling sleep. This is explicit test configuration, not evidence for the default watcher timing.
|
||||
+4
@@ -20,6 +20,8 @@ Required id 为 `agent-loop`、`webserver`、`modules`、`connection`、`headles
|
||||
|
||||
该策略适用于 [Web host 启动](../../../../packages/boot/app-boot/README.zh.md),包括其 [client 插件名册](2026-07-23-client-plugin-loading-model.zh.md)。[按会话的 preset](2026-09-18-declarative-agent-presets.zh.md)持有独立的严格子树审计。
|
||||
|
||||
浏览器启动审计以同一策略审计客户端插件树,使用 [`boot-client.ts`](../../../../packages/client/web/src/boot-client.ts) 中独立的全局名单:模块系统、Session 控制、渲染器、应用框架、Session 视图及其侧栏。浏览器 entry 以包名命名,因此名单写包名;启动图中不存在的名字不产生影响,两份名单互不推导。名单是最小致命集合:已列出 entry 所等待的 entry 同样会阻止启动。功能插件(包括凭据遮挡)保持可选。失败 entry 报告其激活错误。可选失败出现在一条控制台警告中;只有后续图同步调和之后,才会出现在「设置 → 插件 → 插件列表」。重命名名单中的包需要同步更新名单及其测试。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **给 vendored Loader 增加 transactional 与 best-effort mode。** 拒绝,因为严格语义属于应用或资源 owner,而一个 Loader group 包含互不相关的 plugin。Mode 还会扩大 vendor patch,并要求 caller 为每个 group 选择 policy。
|
||||
@@ -36,6 +38,8 @@ Required id 为 `agent-loop`、`webserver`、`modules`、`connection`、`headles
|
||||
|
||||
App-boot 单元测试覆盖缺失和禁用的 required id、optional import failure、config evaluation failure、同步和异步 `apply()` failure、pending dependency,以及 required failure teardown。单元预期输出固定诊断分组、原始错误对象与导入日志的保留、exporter 清理、完整诊断值、私有文件创建、并发报告命名以及写入失败回退行为。构建后的 Web-profile acceptance 断言端口冲突堆栈只输出一次且不包含 Node 包装输出,验证已保存的诊断文件及其 stderr 回退,并会在 optional failure 存在时继续提供完整 UI,并在 required HTTP port 被占用或 `modules`、`connection` 无法激活时以非零码退出,且不报告就绪。
|
||||
|
||||
浏览器审计单元测试(`boot-client.client.spec.ts`、`boot.client.spec.ts`)覆盖必需与可选 entry 的导入、创建、激活与 pending 失败、carrier 呈现,以及启动图中不存在的必需名称。测试载体比产品更严格:除非用例传入 `allowInactiveEntries`,`TestClient` 要求每个 roster entry 都激活;浏览器 e2e 把 `web boot: warning` 视为失败,且不提供放宽。
|
||||
|
||||
[Web 进程矩阵](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts)分别验证启动时和原生补丁文件修改后的 optional 与 required 失败。经过认证的 HTTP 请求和插件生命周期文件区分可用应用与仅存活的进程。这些无需密钥的进程检查与[受控事件投递单元测试](../../../../packages/boot/app-boot/tests/user-patches.spec.ts)互补:单元测试隔离配置协调失败,进程测试还要求随附启动器、原生监听器和有界关闭流程协同工作。
|
||||
|
||||
矩阵启用 Chokidar 的 `awaitWriteFinish`,在每次重载前确认文件内容已稳定;否则它的短暂 change 事件抑制窗口可能丢弃下一次测试编辑。测试仍然依赖原生事件,并等待观察到激活或失败,而不是固定时长的休眠。这是显式测试配置,不能证明默认监听器的时序行为。
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: 3aef37bb4f8a8e53
|
||||
zh: a7b477324d486ab3
|
||||
/agent-note-sandboxed-node-execution-for-ptc/decision/control-and-lifetime:
|
||||
en: 2c7a694b04951772
|
||||
zh: 133b95b0477b127f
|
||||
en: a8897bae00f5f674
|
||||
zh: 2c959353c5a885a4
|
||||
/agent-note-sandboxed-node-execution-for-ptc/decision/resource-limits:
|
||||
en: 3f1d8a30f6d48243
|
||||
zh: faa7c01d7c959893
|
||||
|
||||
@@ -28,7 +28,7 @@ The [experimental Python provider](../../../../packages/experimental/ptc-runtime
|
||||
|
||||
The subprocess owner supplies a dedicated inherited binary control channel, separate from program stdout/stderr and launcher lifecycle IPC. The host bounds frames, queued writes, pending calls and outstanding argument bytes, then validates call identity and the binding allowlist before dispatch. Model code can write to that channel, so its bytes remain untrusted.
|
||||
|
||||
Program completion, timeout, cancellation and protocol failure all close execution through the managed process owner. Result selection stops the execution timer; cleanup then waits for the direct outcome and managed-range quiescence. The PTC bridge separately aborts and drains nested tool dispatches before its outer tool result settles. A denial or transport failure never automatically replays a program whose effects may already have occurred.
|
||||
For temporary executions, program completion, timeout, cancellation and protocol failure all close execution through the managed process owner. Result selection stops the execution timer; cleanup then waits for the direct outcome and managed-range quiescence. The PTC bridge separately aborts and drains nested tool dispatches before its outer tool result settles. A denial or transport failure never automatically replays a program whose effects may already have occurred.
|
||||
|
||||
### Resource limits
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Host 在子进程启动时保留 `ELECTRON_RUN_AS_NODE`;bootstrap 在求值前
|
||||
|
||||
子进程所有者提供专用的继承式二进制控制通道,与程序 stdout/stderr 及 launcher 生命周期 IPC 分开。Host 限制帧、排队写入、待处理调用和未完成参数字节,然后在分派前验证调用身份与绑定允许列表。模型代码可以写入该通道,因此其中字节仍不可信。
|
||||
|
||||
程序完成、超时、取消与协议失败都通过受管进程所有者关闭执行。选择结果后停止执行计时器;清理随后等待直接结果与受管范围停稳。PTC 桥接在外层工具结果结算前,另行取消并排空嵌套工具分派。拒绝或传输失败绝不自动重放可能已经产生副作用的程序。
|
||||
对于临时执行,程序完成、超时、取消与协议失败都通过受管进程所有者关闭执行。选择结果后停止执行计时器;清理随后等待直接结果与受管范围停稳。PTC 桥接在外层工具结果结算前,另行取消并排空嵌套工具分派。拒绝或传输失败绝不自动重放可能已经产生副作用的程序。
|
||||
|
||||
### 资源限制
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 2abacff687661d28
|
||||
zh: 2d7ba3ae27dba888
|
||||
/agent-note-keep-live-configuration-in-the-owning-profile/decision:
|
||||
en: 66c3884425cfb90a
|
||||
zh: 0fe6a1f1a576470b
|
||||
en: f9b1dd3d158457a9
|
||||
zh: 06ca76fcbbc76cda
|
||||
/agent-note-keep-live-configuration-in-the-owning-profile/alternatives-considered:
|
||||
en: 4089464ad1fb60bd
|
||||
zh: 3168e9e4a205145d
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ A plugin declares every configurable value in its Cordis Config. Fields that can
|
||||
|
||||
Form writes target the active profile. Home patches and command-line overlays remain higher-priority deployment inputs; an edit they would shadow fails before persistence. Two plugin instances are addressed by their distinct profile entry ids. Nested Includes retain independent ownership and are not writable through the profile form.
|
||||
|
||||
Cordis config patches replace complete entry configs. An edit preserves ordinary fields and unedited secrets, including raw configuration expressions. A field reset restores the currently inherited value; resetting the complete entry removes its config overrides so later bundle changes are inherited. Other edits store the entry's complete config in the profile row: the ordinary fields as composed at write time plus every volatile field. Later bundle changes to those fields do not reach that profile until the entry's config override is removed. In the shipped bundles this pins `permission.presets` after a default-preset change, `agent-presets` after a preset choice, `agent-loop.agents` after a parallelism edit, and `web-search-deepseek.apiKeyEnv` after a Web Search edit, and the client marks every volatile field of such a row as overridden, not only the edited one. Narrowing a write to the edited fields needs merge semantics for patch `config`, which Include does not provide.
|
||||
Cordis config patches replace complete entry configs. An edit preserves ordinary fields and unedited secrets, including raw configuration expressions. A field reset restores the currently inherited value; resetting the complete entry removes its config overrides so later bundle changes are inherited. Other edits store the entry's complete config in the profile row: the ordinary fields as composed at write time plus every volatile field. Later bundle changes to those fields do not reach that profile until the entry's config override is removed. In the shipped bundles this pins `permission.presets` after a default-preset change, `agent-presets` after a preset choice, `agent-loop.agents` after a parallelism edit, and `web-search-deepseek.apiKeyEnv` after a legacy Messages search edit on the Web search and fetch page, and the client marks every volatile field of such a row as overridden, not only the edited one. Narrowing a write to the edited fields needs merge semantics for patch `config`, which Include does not provide.
|
||||
|
||||
The removed `$DSH_HOME/settings.yaml` is imported once into the active profile when the Loader has settled every entry after Settings starts: section ids are entry ids, with `ui-developer-tools` → `ui-settings`, `ui-onboarding` → `ui-settings-general`, and `shell` → the platform's shell executor entry. The file is renamed to `settings.yaml.imported` before the first write, so the import never repeats; a rejected section is logged and stays in the renamed file.
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
表单写入当前 profile。home patch 和命令行 overlay 仍是优先级更高的部署输入;如果它们会遮蔽某次修改,编辑器会在持久化前拒绝写入。两个插件实例通过各自不同的 profile entry id 寻址。嵌套 Include 保留独立所有权,不能通过该 profile 表单写入。
|
||||
|
||||
Cordis config patch 替换完整 entry config。编辑会保留普通字段、未编辑的秘密值和原始配置表达式。字段重置恢复当前继承值;整个 entry 重置后会移除其 config override,从而继承后续 bundle 变化。其他编辑会把该 entry 的完整 config 存入 profile 行:写入时组合出的普通字段加上全部 volatile 字段。此后 bundle 对这些字段的修改在该 entry 的 config override 被移除之前不会到达该 profile。在已发布的 bundle 中,修改默认权限预设会钉住 `permission.presets`,选择预设会钉住 `agent-presets`,编辑并行度会钉住 `agent-loop.agents`,编辑网页搜索会钉住 `web-search-deepseek.apiKeyEnv`;客户端会把这类行的每个 volatile 字段都标为已覆盖,而不只是被编辑的那个。要把写入收窄到被编辑字段需要 patch `config` 的合并语义,Include 不提供。
|
||||
Cordis config patch 替换完整 entry config。编辑会保留普通字段、未编辑的秘密值和原始配置表达式。字段重置恢复当前继承值;整个 entry 重置后会移除其 config override,从而继承后续 bundle 变化。其他编辑会把该 entry 的完整 config 存入 profile 行:写入时组合出的普通字段加上全部 volatile 字段。此后 bundle 对这些字段的修改在该 entry 的 config override 被移除之前不会到达该 profile。在已发布的 bundle 中,修改默认权限预设会钉住 `permission.presets`,选择预设会钉住 `agent-presets`,编辑并行度会钉住 `agent-loop.agents`,在「网页搜索与获取」页编辑旧版 Messages 搜索会钉住 `web-search-deepseek.apiKeyEnv`;客户端会把这类行的每个 volatile 字段都标为已覆盖,而不只是被编辑的那个。要把写入收窄到被编辑字段需要 patch `config` 的合并语义,Include 不提供。
|
||||
|
||||
被移除的 `$DSH_HOME/settings.yaml` 会在 Settings 启动后、Loader 完成所有条目加载时一次性导入当前 profile:section id 即 entry id,其中 `ui-developer-tools` → `ui-settings`、`ui-onboarding` → `ui-settings-general`、`shell` → 当前平台的 shell 执行器条目。文件在第一次写入前改名为 `settings.yaml.imported`,因此导入不会重复;被拒绝的 section 会记录日志并保留在改名后的文件中。
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
en: 97fe58a13135b4a1
|
||||
zh: 7490373a8d941da5
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/decision:
|
||||
en: 0c40a9283650c643
|
||||
zh: 91a18d752d09a9ce
|
||||
en: 24138152d511bf12
|
||||
zh: 95516d7566b6db7c
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/alternatives-considered:
|
||||
en: b78785f2a603eba8
|
||||
zh: b8c268228da7e2e7
|
||||
en: 311db1f781e386e7
|
||||
zh: 7e3312544154a158
|
||||
/agent-note-active-tool-headers-and-stateful-request-projection/consequences:
|
||||
en: 60d7bf28fa5a8fd1
|
||||
zh: a17b222e7aca01e2
|
||||
en: 8a64dba42c01f647
|
||||
zh: 9e7ba7a5bd855031
|
||||
@@ -14,9 +14,9 @@ Plugin and MCP tools change during conversations. Providers differ in whether th
|
||||
|
||||
`Session.toolHistory()` caches a capability-independent fold of committed headers and developer messages. First access restores inherited events; later reads consume only unseen events. Immutable snapshots carry initial declarations and ordered update identities with additions resolved through their historical headers. Explicit series starts and changed retained definitions rebuild declarations. Restoring a retained name with an unchanged definition continues the series: the projection keeps the declaration unchanged and lets the recorded addition re-offer the tool after its earlier removal, so a removal never disables a declared tool and a loading mode never rewrites an earlier declaration.
|
||||
|
||||
Loop and compaction requests carry this snapshot as `GenerateOptions.toolHistory`. At adapter dispatch, `projectToolUpdates` uses the prepared route's `toolUpdate`. Unsupported routes receive active definitions without `deferLoading` or developer messages. Supported routes defer historical additions; `in-history` retains removed definitions and removal blocks, while `addition-only` omits both. Only current-series update identities survive. Missing history or a request prefix omitting recorded updates uses current declarations without developer updates. Explicit deferred loading remains available on capable routes.
|
||||
Loop and compaction requests carry this snapshot as `GenerateOptions.toolHistory`. At adapter dispatch, `projectToolUpdates` uses the prepared route's `toolUpdate`. Unsupported routes receive active definitions without `deferLoading` or developer messages. Supported routes defer historical additions; `in-history` retains removed definitions and removal blocks, while `addition-only` omits both. On addition-only routes, removing any tool rebuilds the route's baseline after that update: the request declarations change even when the removed tool was activated later, so retaining superseded activation blocks cannot preserve the full conversation prefix. Adding tools when no declarations exist also rebuilds the baseline. Deferred declarations count as existing tools. The first native tool changes the empty declaration prefix; deferring it provides no cache advantage over declaring it immediately. Previously activated tools become immediate; explicitly deferred declarations with no activation remain deferred. Subsequent additions remain deferred until their recorded activation. Superseded tool blocks are omitted without discarding other content in those messages. The Session fold and durable events remain capability-independent. Missing history or a request prefix omitting recorded updates uses current declarations without developer updates.
|
||||
|
||||
DeepSeek translates projected updates into system-role `tool_addition` and `tool_removal` blocks and `defer_loading` declarations, sending the tool-changes beta header when update blocks occur. The default `deepseek-flash` entry declares `addition-only`; pi-ai declares no mode. Chat and Trajectory display developer messages as context nodes, using localized tool-change notices: single changes show the tool name; multiple changes show counts and expandable name lists.
|
||||
DeepSeek translates projected updates into system-role `tool_addition` and `tool_removal` blocks and `defer_loading` declarations, sending the tool-changes beta header when update blocks occur. The default `deepseek-flash` entry declares `addition-only`; pi-ai selects `in-history` for compatible Anthropic routes and `addition-only` for compatible Chat Completions and Responses routes. Chat and Trajectory display developer messages as context nodes, using localized tool-change notices: single changes show the tool name; multiple changes show counts and expandable name lists.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,6 +26,8 @@ DeepSeek translates projected updates into system-role `tool_addition` and `tool
|
||||
|
||||
**Scan the complete log on every request.** The cached fold consumes new events and supplies stable snapshots to ordinary and auxiliary calls without adding arbitrary synchronous history readers.
|
||||
|
||||
**Rebuild the baseline on every removal.** Deferred tools also have request declarations; deleting a tool changes those declarations regardless of its activation position. Addition-only routes cannot express that deletion in history. The rebuild therefore also covers removal-only updates and removals of later-activated tools. A past reset cannot force every future addition to become immediate: each reset establishes a new baseline from which deferred additions resume.
|
||||
|
||||
**Reject same-name schema changes during tool registration.** This restriction is not enforced. Immediate failure for a plugin registering an incompatible tool requires comparing the new registration with historical definitions of tools called on each affected current surface, while excluding calls removed by compaction. That registration-time historical and scope-aware check is deferred because of its implementation complexity. Same-name schema changes remain allowed; they rebuild declarations and invalidate prefix reuse even on `in-history` routes.
|
||||
|
||||
## Consequences
|
||||
@@ -34,4 +36,6 @@ Tool lists and logical update events are capability-independent; only outgoing d
|
||||
|
||||
The authored [headless prompt/tool scenario](../../../../snapshots/session/dynamic-tool-prompt-updates/snapshot.yml) pins an appended prompt beside a deferred tool addition and verifies that the first request's messages stay unchanged. It shares the stable-prompt scenario's tool lifecycle and schema expectations.
|
||||
|
||||
The [DeepSeek replacement scenario](../../../../snapshots/session/deepseek-deferred-tools/snapshot.yml) checks the first additions to an empty set, a rebuilt baseline, a subsequent deferred addition, and removal of that later-activated tool through Messages serialization. Explicit deferred-only requests remain unchanged under the [DeepSeek failure policy](../simplification/2026-11-02-deepseek-deferred-tool-failures.md). These checks establish request construction, not measured cache savings from rebuilding.
|
||||
|
||||
The [real DeepSeek SDK test](../../../../apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts) measures the first request after a native tool addition with both unchanged and simultaneously updated instructions. Unique user history prevents a shared system-prompt cache hit from satisfying the preceding-input assertion. Removal checks tool availability without requiring cache retention; each case disables retries.
|
||||
@@ -14,9 +14,9 @@ Status: implemented
|
||||
|
||||
`Session.toolHistory()` 缓存对已提交请求头和 developer 消息的归约,不依赖模型能力。首次访问恢复继承事件;后续读取仅处理新增事件。不可变快照携带初始声明和有序更新标识,添加定义通过历史请求头解析。显式新序列和保留定义变化会重建声明。以未变化的定义恢复保留名称时序列继续:投影保持声明不变,由记录的添加在先前移除之后重新提供该工具,因此移除不会禁用已声明的工具,加载模式也不会改写先前声明。
|
||||
|
||||
循环和压缩请求通过 `GenerateOptions.toolHistory` 携带此快照。适配器分发时,`projectToolUpdates` 使用已准备路由的 `toolUpdate`。不支持的路由接收有效定义,不携带 `deferLoading` 或 developer 消息。支持的路由延迟历史添加;`in-history` 保留已移除定义和移除块,`addition-only` 则省略两者。仅保留当前序列的更新标识。缺少历史或请求前缀遗漏已记录更新时,使用当前声明且不发送 developer 更新。显式延迟加载在支持的路由上仍然可用。
|
||||
循环和压缩请求通过 `GenerateOptions.toolHistory` 携带此快照。适配器分发时,`projectToolUpdates` 使用已准备路由的 `toolUpdate`。不支持的路由接收有效定义,不携带 `deferLoading` 或 developer 消息。支持的路由延迟历史添加;`in-history` 保留已移除定义和移除块,`addition-only` 则省略两者。在 addition-only 路由移除任何工具时,使用该次更新后的状态重建路由基线:即使被移除工具是后来激活的,请求声明也已发生变化,保留已被取代的激活块无法保留完整对话前缀。不存在任何声明时添加工具,也会重建基线。延迟声明也计入已有工具。首个原生工具会改变空声明前缀;将其延迟加载并不比直接声明具有缓存优势。此前已激活工具变为立即加载;没有激活记录的显式延迟声明仍保持延迟。后续添加在记录的激活位置前保持延迟加载。已被取代的工具块会被省略,但不会丢弃这些消息中的其他内容。Session 归约和持久事件仍不依赖模型能力。缺少历史或请求前缀遗漏已记录更新时,使用当前声明且不发送 developer 更新。
|
||||
|
||||
DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `tool_removal` 块以及 `defer_loading` 声明,在存在更新块时发送工具变更 beta 请求头。默认 `deepseek-flash` 条目声明 `addition-only`;pi-ai 不声明模式。Chat 和 Trajectory 将 developer 消息显示为上下文节点,工具变更使用本地化通知:单项变更显示工具名,多项变更显示数量及可展开的名称列表。
|
||||
DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `tool_removal` 块以及 `defer_loading` 声明,在存在更新块时发送工具变更 beta 请求头。默认 `deepseek-flash` 条目声明 `addition-only`;pi-ai 为兼容的 Anthropic 路由选择 `in-history`,为兼容的 Chat Completions 和 Responses 路由选择 `addition-only`。Chat 和 Trajectory 将 developer 消息显示为上下文节点,工具变更使用本地化通知:单项变更显示工具名,多项变更显示数量及可展开的名称列表。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,6 +26,8 @@ DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `to
|
||||
|
||||
**每次请求扫描完整日志。** 缓存归约处理新事件,为普通和辅助调用提供稳定快照,不增加任意同步历史读取接口。
|
||||
|
||||
**每次移除都重建基线。** 延迟工具也具有请求声明;删除工具会改变这些声明,与其激活位置无关。addition-only 路由无法通过历史表达删除。因此,仅移除的更新和移除后来激活的工具也会重建基线。一次历史重置不能让所有后续添加都变为立即加载:每次重置确立新基线,后续添加继续采用延迟加载。
|
||||
|
||||
**在工具注册时拒绝同名 schema 变化。** 当前不执行此限制。要让注册不兼容工具的插件立即收到异常,需要将新注册与各受影响当前 surface 中已调用工具的历史定义比较,同时排除已被压缩移除的调用。由于实现复杂,注册时结合历史和作用域的检查留待后续实现。同名 schema 变化仍被允许;它们会重建声明,即使在 `in-history` 路由上也会使前缀复用失效。
|
||||
|
||||
## Consequences
|
||||
@@ -34,4 +36,6 @@ DeepSeek 把投影后的更新转换为 system 角色的 `tool_addition` 和 `to
|
||||
|
||||
手写的[无界面提示词/工具场景](../../../../snapshots/session/dynamic-tool-prompt-updates/snapshot.yml)钉住追加提示词与延迟工具添加同时发生的行为,并验证首次请求的消息保持不变。它复用稳定提示词场景的工具生命周期和 schema 期望值。
|
||||
|
||||
[DeepSeek 替换场景](../../../../snapshots/session/deepseek-deferred-tools/snapshot.yml)通过 Messages 序列化验证向空工具集首次添加工具、重建后的基线、后续延迟添加,以及移除该后来激活的工具。根据 [DeepSeek 失败策略](../simplification/2026-11-02-deepseek-deferred-tool-failures.zh.md),显式全延迟请求保持原样。这些检查验证请求构造,不代表已经测量重建带来的缓存收益。
|
||||
|
||||
[真实 DeepSeek SDK 测试](../../../../apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts) 分别在指令不变和指令同时更新时测量原生工具新增后的首次请求。独有的用户历史确保仅命中共享系统提示词缓存无法满足前序输入断言。移除场景检查工具可用性,不要求保留缓存;每个用例均禁用重试。
|
||||
+4
-4
@@ -36,8 +36,8 @@
|
||||
en: 21a74f9031f86a92
|
||||
zh: c6abd0a5dfbf56a8
|
||||
/agent-note-definition-owned-grouping-with-generic-node-group-assembly/verification:
|
||||
en: b42409d029fc0fde
|
||||
zh: 0f8ab7d206cab772
|
||||
en: ff829b9a2772df95
|
||||
zh: df0e6524d00034df
|
||||
/agent-note-definition-owned-grouping-with-generic-node-group-assembly/consequences:
|
||||
en: 96f1d81b28534661
|
||||
zh: 8cb1c7a0f000c335
|
||||
en: 236ddec90acc6ca1
|
||||
zh: d4807f635e217766
|
||||
@@ -159,7 +159,7 @@ Business behavior is verified through keyed-update regressions and recorded Web
|
||||
| Evidence | Required observation |
|
||||
|---|---|
|
||||
| Keyed notifications and React updates | Content growth affects the owning Node and affected Group; unchanged historical rows and unrelated Turns receive no extra notifications. Mode changes preserve keys and member parents. |
|
||||
| Recorded Web replay | Current accessible titles, group disclosure, hidden ordinary Context, trigger notices, and footer placement match the committed expected output. |
|
||||
| Recorded Web replay | Current accessible titles, group disclosure, Context rows, trigger notices, and footer placement match the committed expected output. |
|
||||
|
||||
[Recorded Web scenarios](../../../../apps/web/tests/steering.e2e.ts) cover online steering, reconnect handoff, and grouped presentation through the shipped Web profile. These behavior checks do not establish a quantified latency or memory improvement.
|
||||
|
||||
@@ -171,4 +171,4 @@ Business behavior is verified through keyed-update regressions and recorded Web
|
||||
- Stable mounting does not eliminate layout, paint, or retained-memory costs. No measured latency or optimal-performance claim is made.
|
||||
- Real group splits, merges, first-member changes, and pagination repairs can change identity. The mode-switch guarantee does not prohibit those legitimate changes.
|
||||
- Revealing hidden parts, selection/copy, interruption notices, group disclosure, and outer Turn interaction belong to Chat's business adaptation; generic reference tests alone do not establish those behaviors.
|
||||
- Turn status/duration replaces the old tool/message-count title, so status, accessible names, and empty-process Turns require explicit presentation coverage. Hiding ordinary Context retains non-human waking notices and original Session/Trajectory inspection; footer placement depends on the real Turn end rather than group membership.
|
||||
- Turn status/duration replaces the old tool/message-count title, so status, accessible names, and empty-process Turns require explicit presentation coverage. Non-human waking notices remain independent of Context rows, and Session/Trajectory inspection is unchanged; footer placement depends on the real Turn end rather than group membership.
|
||||
@@ -159,7 +159,7 @@ React key 由引用 kind、NodeKey/groupPart 或 GroupKey 的无歧义元组派
|
||||
| 证据 | 必须观察到的结果 |
|
||||
|---|---|
|
||||
| 按键通知与 React 更新 | 正文增长影响所属 Node 和相关 Group;未变化历史行及其他 Turn 没有额外通知。模式变化保留 key 与成员父级。 |
|
||||
| 已录制的 Web 回放 | 当前可访问标题、组开合、普通 Context 隐藏、触发通知及页脚位置与提交的预期输出一致。 |
|
||||
| 已录制的 Web 回放 | 当前可访问标题、组开合、Context 行、触发通知及页脚位置与提交的预期输出一致。 |
|
||||
|
||||
[已录制的 Web 场景](../../../../apps/web/tests/steering.e2e.ts)通过正式 Web profile 覆盖在线 steering、重连接续与分组展示。这些行为检查不构成量化延迟或内存改善的证明。
|
||||
|
||||
@@ -171,4 +171,4 @@ React key 由引用 kind、NodeKey/groupPart 或 GroupKey 的无歧义元组派
|
||||
- 稳定挂载不消除布局、绘制或保留内存的成本,不宣称实测延迟或最优性能。
|
||||
- 真实组拆并、首成员变化及分页修正可以改变身份,模式切换保证不禁止这些正常变化。
|
||||
- 隐藏部分的揭示、选择复制、中断提示、组开合与外层 Turn 联动属于 Chat 业务适配,仅凭通用引用测试不能确认这些行为。
|
||||
- 整轮状态与用时替代旧工具/消息计数标题,因此状态、可访问名称和空过程轮次需要明确的展示覆盖。隐藏普通 Context 时保留非人工唤醒通知及原始 Session/Trajectory 查看能力;页脚定位依赖真实 Turn 结束,不依赖分组成员。
|
||||
- 整轮状态与用时替代旧工具/消息计数标题,因此状态、可访问名称和空过程轮次需要明确的展示覆盖。非人工唤醒通知独立于 Context 行,Session/Trajectory 查看能力不变;页脚定位依赖真实 Turn 结束,不依赖分组成员。
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: e42db40b879fdbf4
|
||||
zh: 6017da9c4da17835
|
||||
/agent-note-node-ptc-execution-modes-and-in-memory-state/decision:
|
||||
en: 7b5808a31493ac1e
|
||||
zh: 8d9c698c41dbf18e
|
||||
en: 411054071757d5fe
|
||||
zh: e683ef7d80f33fc6
|
||||
/agent-note-node-ptc-execution-modes-and-in-memory-state/alternatives-considered:
|
||||
en: 396157a9fc293198
|
||||
zh: a061f05b4423e26f
|
||||
|
||||
@@ -18,7 +18,7 @@ One npm package and one plugin entry keep existing installation and loading path
|
||||
|
||||
The Session records submitted programs, nested tool calls and returned output. It does not serialize the REPL heap or rerun earlier programs after restart, resume or fork: replaying automation could repeat external side effects. A missing variable is a normal program error, and the model-visible instructions require recreating variables and reacquiring resources. No environment identifier or new durable Session event is required because the provider exposes no unlogged model input.
|
||||
|
||||
Cancellation, deadline expiry, protocol or output-limit failure, owner disposal and process exit discard the environment. Ordinary exceptions retain earlier mutations. A retained function uses the active evaluation's host bindings; asynchronous work from an earlier evaluation cannot acquire a later evaluation's tool authority or output sink.
|
||||
Protocol or output-limit failure, owner disposal and process exit discard the environment. The [interruption decision](2026-10-29-retained-ptc-interruption.md) owns timeout and cancellation recovery with retained state. Ordinary exceptions retain earlier mutations. A retained function uses the active evaluation's host bindings; asynchronous work from an earlier evaluation cannot acquire a later evaluation's tool authority or output sink.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
Session 记录提交的程序、嵌套工具调用和返回输出。它不序列化 REPL 堆,也不在重启、恢复或派生后重新执行旧程序,因为重放自动化可能重复外部操作。缺失变量产生普通程序错误,模型指引要求重新创建变量并获取资源。提供方没有引入日志之外的模型输入,因此不需要环境标识或新的持久 Session 事件。
|
||||
|
||||
取消、截止时间到期、协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
|
||||
协议失败、输出超限、所有者销毁和进程退出都会丢弃环境。[中断决策](2026-10-29-retained-ptc-interruption.zh.md)负责保留状态的超时和取消恢复。普通异常保留此前发生的修改。保留的函数使用当前执行的宿主绑定;较早执行中的异步任务不能获取后续执行的工具权限或向其输出。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
+8
-8
@@ -6,14 +6,14 @@
|
||||
en: 9163af91ea0e030d
|
||||
zh: 92e44043b999d32f
|
||||
/agent-note-offer-web-search-and-web-fetch-only-while-the-selected-provider-is-ready/problem:
|
||||
en: 6fac3c73a4415c83
|
||||
zh: f65b61cc1705348b
|
||||
en: 32ba2b2b42e8deb5
|
||||
zh: 6a6532561f81b8e2
|
||||
/agent-note-offer-web-search-and-web-fetch-only-while-the-selected-provider-is-ready/decision:
|
||||
en: 458416ed7e47f644
|
||||
zh: 8b4dd820228e9dfd
|
||||
en: 08a54b37e157da29
|
||||
zh: 65f633055d9df242
|
||||
/agent-note-offer-web-search-and-web-fetch-only-while-the-selected-provider-is-ready/alternatives-considered:
|
||||
en: b731254983be656d
|
||||
zh: 5dee2b09f7faa537
|
||||
en: 6da221f6b821ea44
|
||||
zh: b15774011abed7ed
|
||||
/agent-note-offer-web-search-and-web-fetch-only-while-the-selected-provider-is-ready/consequences:
|
||||
en: 798faeaa0b406eac
|
||||
zh: 97bbfdaebc84b1d4
|
||||
en: c90a122c1f33bb83
|
||||
zh: 0175ca0f599d086b
|
||||
+8
-8
@@ -6,17 +6,17 @@ English | [中文](2026-09-27-web-search-offered-by-provider-readiness.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`dsh-tool-web` registers `web_search` whenever search is enabled, and the [web capability seam decision](2026-06-24-web-capability-seam.md) keeps it visible while the selected provider cannot run. The shipped search provider, `dsh-web-search-deepseek`, authenticates a search with the DeepSeek account token only for a model request on the `deepseek-account` route; every other route needs a configured API key. A Session on an API-key route without a search key was therefore offered a tool whose every call failed with `WEB_PROVIDER_CREDENTIAL_MISSING`. Sessions in one Host can use different routes, a Session can switch routes between requests, and the Web app registers `web_search` once per agent preset, so no registration decision matches every request. The Search and Fetch APIs of `dsh-web-deepseek-api`, which the Web search settings switch selects together, need that credential for both operations, so the same Session was also offered a `web_fetch` whose every call failed.
|
||||
`dsh-tool-web` registers `web_search` whenever search is enabled, and the [web capability seam decision](2026-06-24-web-capability-seam.md) keeps it visible while the selected provider cannot run. The DeepSeek web providers authenticate an operation with the DeepSeek account token only for a model request on the `deepseek-account` route; every other route needs a configured API key. Without a readiness check, a Session on an API-key route without a key is offered a tool whose every call fails with `WEB_PROVIDER_CREDENTIAL_MISSING`. Sessions in one Host can use different routes, a Session can switch routes between requests, and the Web app registers `web_search` once per agent preset, so no registration decision matches every request. The Fetch API of `dsh-web-deepseek-api`, which the user selects separately from the search provider ([provider selection](2026-10-29-web-provider-selection.md)), needs the same credential, so such a Session with it selected would also be offered a `web_fetch` whose every call fails.
|
||||
|
||||
## Decision
|
||||
|
||||
Each role of the web capability seam owns one part of a per-step check:
|
||||
|
||||
- **Service Definition.** `WebSearchProvider` and `WebFetchProvider` have an optional `ready(llmProvider)` that answers whether an operation made for a model request on that chat route has a credential to send. It checks configuration only and makes no network calls. `ctx.web.searchReady(llmProvider?)` and `ctx.web.fetchReady(llmProvider?)` resolve the provider with the same rules as `search()` and `fetch()` and return its answer. A provider without `ready()` counts as ready, and a selection failure answers true because the operation reports that failure as a structured `WebError`.
|
||||
- **Service Provider.** `dsh-web-search-deepseek` answers true when the route is `deepseek-account` and `ctx.deepseekAccount.resolveToken()` returns a token for the configured search endpoint, when the section carries a literal `apiKey`, or when `ctx.credentials.describe()` reports the `apiKeyEnv` reference configured (without that service, when the launching environment carries it). It checks credentials in the order `search()` resolves them, and a failed account-token or credential-store read answers false, because `search()` fails on that read instead of trying the next credential. The Search and Fetch API providers of `dsh-web-deepseek-api` share one credential and answer the same way for their API base; their transport repeats these checks, so neither package depends on the other. The providers name no tool or prompt section.
|
||||
- **Service Provider.** Each DeepSeek provider checks its credentials in the order its operation resolves them. `dsh-web-search-deepseek` checks, on the `deepseek-account` route, whether `ctx.deepseekAccount.resolveToken()` returns a token for its Messages endpoint, and then its literal `apiKey` or its `apiKeyEnv` reference. The Search and Fetch API providers of `dsh-web-deepseek-api` resolve the same plan as the operation ([credential resolution](2026-10-29-web-provider-selection.md)): a capability with its own YAML fields checks only the key under its `*ApiKeyEnv`; otherwise, on the account route with the account model route mounted, a token for that route's `/v1/tools` base counts, and then the key of the `deepseek-official` route, or `DEEPSEEK_API_KEY` without that route. A key counts when `ctx.credentials.describe()` reports its reference configured (without that service, when the launching environment carries it); the [shared credential helpers](../../../../packages/llm/llm-deepseek/src/credential.ts) perform these presence checks. A failed account-token or credential-store read answers false, because the operation fails on that read instead of trying the next credential. The providers name no tool or prompt section.
|
||||
- **Consumer.** `dsh-tool-web` registers `web_search` and `web_fetch` with [step activation conditions](../../../../packages/core/tools/README.md#conditional-tools) that ask `ctx.web.searchReady()` and `ctx.web.fetchReady()` about the route of the step's model selection. The agent loop resolves conditions before it assembles the step, so the declared tools, the `tool:web_search` and `tool:web_fetch` prompt sections, PTC program bindings, and dispatch all read the same admitted set.
|
||||
|
||||
The route decides only whether the account token is checked before the API key, which happens on the `deepseek-account` route; an API key counts on every route, so a conversation on another provider's model is offered the DeepSeek web tools whenever a DeepSeek key is configured. The providers do not compare the chat route's endpoint with their own: each operation sends the configured key, `DEEPSEEK_API_KEY` by default, to the configured endpoint, even when the official route chats through a gateway.
|
||||
The route decides only whether the account token is checked before the API key, which happens on the `deepseek-account` route; an API key counts on every route, so a conversation on another provider's model is offered the DeepSeek web tools whenever a DeepSeek key is configured. A Search or Fetch API capability with its own YAML fields ignores the route and is ready exactly when its own key is configured.
|
||||
|
||||
Only presence decides: no API key value is read and no endpoint is contacted, so a wrong key or an endpoint that does not serve the native `web_search_20250305` server tool keeps the tool admitted. A step that does not admit a web tool also refuses calls to it as `UNKNOWN_TOOL`, so a model that repeats an earlier call reaches no provider; an operation made outside a model step resolves its credential when it runs and fails with `WEB_PROVIDER_CREDENTIAL_MISSING` when there is none.
|
||||
|
||||
@@ -24,7 +24,7 @@ The request header records the assembled tools, so the agent loop announces each
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Let the DeepSeek provider remove `web_search` in its own assemble listener.** The seam stays unchanged, but the provider then names another package's tool, prompt section, and PTC transport, and it removes the tool even when the deployment selects another search provider, such as Exa, whose searches would succeed.
|
||||
**Let the DeepSeek provider remove `web_search` in its own assemble listener.** The seam stays unchanged, but the provider then names another package's tool, prompt section, and PTC transport, and it removes the tool even when the deployment selects another search provider, such as one a plugin registers, whose searches would succeed.
|
||||
|
||||
**Extend `available()`.** It is synchronous and takes no request, while this check reads an asynchronous credential store and depends on the request's route. A route-dependent `available()` would also make provider selection depend on the conversation.
|
||||
|
||||
@@ -36,9 +36,9 @@ The request header records the assembled tools, so the agent loop announces each
|
||||
|
||||
**Offer search only on the DeepSeek account route.** Account tokens are issued only for the official endpoint, so a gateway key could never reach DeepSeek. But every shipped profile defaults to the `deepseek-official` route, so search would be off by default, including the official API-key case.
|
||||
|
||||
**Withhold the tools from other providers' conversations unless search has a key of its own.** A search from another model's conversation bills the DeepSeek key, but DeepSeek search is one of the services that key pays for, and a user who configured it expects search to work whatever model chats. The Web search settings page saves its key under `apiKeyEnv`, `DEEPSEEK_API_KEY` by default, which the chat route also reads, so a key entered there could never serve another model; a `routes` setting that opens them would be one more thing a user has to find.
|
||||
**Withhold the tools from other providers' conversations unless search has a key of its own.** A search from another model's conversation bills the DeepSeek key, but DeepSeek search is one of the services that key pays for, and a user who configured it expects search to work whatever model chats. The Search and Fetch APIs send the chat route's own key, and the settings page saves the legacy Messages search key under `apiKeyEnv`, `DEEPSEEK_API_KEY` by default, which the chat route also reads, so withholding them would leave a configured key unable to serve another model; a `routes` setting that opens them would be one more thing a user has to find.
|
||||
|
||||
**Compare the official chat route's endpoint with the search endpoint.** Withholding the tools or the key when the two endpoints differ in origin keeps a gateway's key away from DeepSeek, but the providers would have to find the chat route's configuration themselves. Taking the first active `dsh-llm-deepseek-api-key` Loader row answers for an unrelated instance when a composition isolates several, which disables a correct route or lets a gateway key through; only the route's owner knows which endpoint serves a request, and an endpoint field in `dsh-llm` would reach the browser through the Remote `listProviders()`. The check would also withhold search from users who chat through a proxy or development endpoint with a key the official search endpoint accepts. Without it, a gateway user's search fails at the configured endpoint.
|
||||
**Compare the official chat route's endpoint with the web endpoint and withhold the tools when they differ.** It would keep a gateway's key away from DeepSeek, but it would also withhold web tools from users who chat through a proxy or development endpoint with a key the web endpoint accepts. The Search and Fetch APIs instead derive their endpoint from the origin of the route whose key they send, so the key and endpoint come from one route; legacy Messages search keeps its own endpoint, where a gateway user's search fails.
|
||||
|
||||
**Keep the tool offered and fail at execution.** The model spends a call on a search that cannot authenticate, and the failure repeats on every attempt.
|
||||
|
||||
@@ -46,6 +46,6 @@ The request header records the assembled tools, so the agent loop announces each
|
||||
|
||||
## Consequences
|
||||
|
||||
`dsh-web` gains public members: the optional `ready()` of both provider interfaces, `WebRuntime.searchReady()`, and `WebRuntime.fetchReady()`. Each readiness method is the smallest query its one consumer needs, which is how the [observation-surface decision](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) lets a status query return; neither reports availability or selection failures. The Exa, Perplexity, and HTTP fetch providers implement no `ready()`, so their behavior is unchanged, and `web_fetch` stays admitted whenever the switch leaves HTTP fetch selected. A step that does not admit a web tool leaves it out of PTC program bindings too. An endpoint that cannot serve the native server tool keeps the tool admitted and fails each call. Removing a tool on an unchanged route changes the declared tools and misses the prompt cache once; a model switch already invalidates it. Searches and fetches from other providers' conversations bill the configured DeepSeek key, and a deployment whose official route chats through a gateway sends the gateway's key to the configured endpoint, the official one by default, where each operation fails unless that endpoint accepts the key. The DeepSeek web packages read no Loader rows and depend on no LLM package.
|
||||
`dsh-web` gains public members: the optional `ready()` of both provider interfaces, `WebRuntime.searchReady()`, and `WebRuntime.fetchReady()`. Each readiness method is the smallest query its one consumer needs, which is how the [observation-surface decision](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) lets a status query return; neither reports availability or selection failures. The HTTP fetch provider implements no `ready()`, so `web_fetch` stays admitted whenever HTTP fetch is selected. A step that does not admit a web tool leaves it out of PTC program bindings too. An endpoint that cannot serve the native server tool keeps the tool admitted and fails each call. Removing a tool on an unchanged route changes the declared tools and misses the prompt cache once; a model switch already invalidates it. Searches and fetches from other providers' conversations bill the configured DeepSeek key. A deployment whose official route chats through a gateway sends the gateway's key to that gateway's origin with `/v1/tools` for the Search and Fetch APIs, and to the configured Messages endpoint, the official one by default, for legacy Messages search, where each search fails unless that endpoint accepts the key. The Search and Fetch APIs read the DeepSeek model routes' Loader entries through the model provider directory.
|
||||
|
||||
Unit tests pin each role: `packages/web/web/tests/web.spec.ts` covers which provider `searchReady()` and `fetchReady()` ask and their answer on selection failure, `packages/web/web-search-deepseek/tests/deepseek.spec.ts` and `packages/web/web-deepseek-api/tests/readiness.spec.ts` cover each credential case of `ready()` on the account route and on other routes, and `packages/web/tool-web/tests/tool-web.spec.ts` resolves step admission of both tools through the real prompt, tool, and executor services, including the `UNKNOWN_TOOL` refusal of a call in a step that withheld the tool. In real compositions, the shipped headless keyless smoke test and the `web-search-unconfigured` recorded-session snapshot, whose search key references are unset, pin the withheld search tool. Snapshot replays supply a placeholder DeepSeek key, so their tool lists do not depend on the runner's environment; with it, the `pi-ai-conversation-updates` conversation on a pi-ai route keeps `web_search`.
|
||||
Unit tests pin each role: [web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) covers which provider `searchReady()` and `fetchReady()` ask and their answer on selection failure, [deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) and [credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) cover each credential case of `ready()` on the account route and on other routes, including readiness that matches each operation, and [tool-web.spec.ts](../../../../packages/web/tool-web/tests/tool-web.spec.ts) resolves step admission of both tools through the real prompt, tool, and executor services, including the `UNKNOWN_TOOL` refusal of a call in a step that withheld the tool. In real compositions, the shipped headless keyless smoke test and the `web-search-unconfigured` recorded-session snapshot, whose search key references are unset, pin the withheld search tool. Snapshot replays supply a placeholder DeepSeek key, so their tool lists do not depend on the runner's environment; with it, the `pi-ai-conversation-updates` conversation on a pi-ai route keeps `web_search`.
|
||||
+8
-8
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## Problem
|
||||
|
||||
`dsh-tool-web` 在启用搜索时总会注册 `web_search`,而 [web 能力 seam 决策](2026-06-24-web-capability-seam.zh.md)让它在选中的提供方无法运行时仍然可见。发行版的搜索提供方 `dsh-web-search-deepseek` 只对 `deepseek-account` 路由上的模型请求使用 DeepSeek 账号 token 鉴权;其他路由都需要已配置的 API 密钥。因此,API 密钥路由上没有搜索密钥的会话会获得一个每次调用都以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败的工具。同一 Host 中的会话可以使用不同路由,一个会话可以在请求之间切换路由,而 Web 应用按 agent preset 只注册一次 `web_search`,因此任何注册层面的决定都无法与每个请求相符。Web 搜索设置开关会同时选中 `dsh-web-deepseek-api` 的 Search 与 Fetch API,两个操作都需要这份凭据,因此同一会话还会获得一个每次调用都失败的 `web_fetch`。
|
||||
`dsh-tool-web` 在启用搜索时总会注册 `web_search`,而 [web 能力 seam 决策](2026-06-24-web-capability-seam.zh.md)让它在选中的提供方无法运行时仍然可见。DeepSeek Web 提供方只对 `deepseek-account` 路由上的模型请求使用 DeepSeek 账号 token 为操作鉴权;其他路由都需要已配置的 API 密钥。没有就绪检查时,API 密钥路由上没有密钥的会话会获得一个每次调用都以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败的工具。同一 Host 中的会话可以使用不同路由,一个会话可以在请求之间切换路由,而 Web 应用按 agent preset 只注册一次 `web_search`,因此任何注册层面的决定都无法与每个请求相符。`dsh-web-deepseek-api` 的 Fetch API 由用户与搜索提供方分别选择([提供方选择](2026-10-29-web-provider-selection.zh.md)),它需要同一份凭据,因此这样的会话选中它时,还会获得一个每次调用都失败的 `web_fetch`。
|
||||
|
||||
## Decision
|
||||
|
||||
web 能力 seam 的每个角色各负责逐步骤检查的一部分:
|
||||
|
||||
- **Service Definition。** `WebSearchProvider` 与 `WebFetchProvider` 有一个可选的 `ready(llmProvider)`,回答为该对话路由上的模型请求发起的操作是否有可发送的凭据。它只检查配置,不发起网络调用。`ctx.web.searchReady(llmProvider?)` 与 `ctx.web.fetchReady(llmProvider?)` 按与 `search()`、`fetch()` 相同的规则解析提供方并返回其回答。没有 `ready()` 的提供方视为就绪;选择失败时回答 true,因为对应操作会把该失败报告为结构化 `WebError`。
|
||||
- **Service Provider。** `dsh-web-search-deepseek` 在以下任一情况下回答 true:路由为 `deepseek-account` 且 `ctx.deepseekAccount.resolveToken()` 为配置的搜索端点返回 token;配置段带有字面 `apiKey`;或 `ctx.credentials.describe()` 报告 `apiKeyEnv` 引用已配置(没有该服务时,启动环境中存在该变量)。它按 `search()` 解析凭据的顺序检查凭据,读取账号 token 或凭据存储失败时回答 false,因为 `search()` 会在该次读取上失败,而不会尝试下一种凭据。`dsh-web-deepseek-api` 的 Search 与 Fetch API 提供方共用一份凭据,并对其 API 基础地址以同样方式回答;它们的传输层重复了这些检查,因此两个包互不依赖。提供方不点名任何工具或提示词段落。
|
||||
- **Service Provider。** 每个 DeepSeek 提供方都按其操作解析凭据的顺序检查凭据。`dsh-web-search-deepseek` 在 `deepseek-account` 路由上检查 `ctx.deepseekAccount.resolveToken()` 是否为其 Messages 端点返回 token,然后检查其字面 `apiKey` 或 `apiKeyEnv` 引用。`dsh-web-deepseek-api` 的 Search 与 Fetch API 提供方解析与操作相同的方案([凭据解析](2026-10-29-web-provider-selection.zh.md)):在 YAML 中设置了单独字段的能力只检查其 `*ApiKeyEnv` 下的密钥;否则,在账号路由上且账号模型路由已挂载时,该路由 `/v1/tools` 基础地址的 token 算数,然后是 `deepseek-official` 路由的密钥,没有该路由时为 `DEEPSEEK_API_KEY`。`ctx.credentials.describe()` 报告其引用已配置时(没有该服务时,启动环境中存在该变量),密钥即算存在;这些存在性检查由[共享凭据函数](../../../../packages/llm/llm-deepseek/src/credential.ts)执行。读取账号 token 或凭据存储失败时回答 false,因为操作会在该次读取上失败,而不会尝试下一种凭据。提供方不点名任何工具或提示词段落。
|
||||
- **Consumer。** `dsh-tool-web` 注册 `web_search` 与 `web_fetch` 时各带一个[步骤激活条件](../../../../packages/core/tools/README.zh.md#conditional-tools),以该步骤模型选择的路由询问 `ctx.web.searchReady()` 与 `ctx.web.fetchReady()`。agent loop 在组装该步骤之前解析各条件,因此声明的工具、`tool:web_search` 与 `tool:web_fetch` 提示词段落、PTC 程序绑定与派发都读取同一个准入集合。
|
||||
|
||||
路由只决定是否在 API 密钥之前检查账号 token,这只发生在 `deepseek-account` 路由上;API 密钥在任何路由上都算数,因此只要配置了 DeepSeek 密钥,使用其他提供方模型的对话也会获得 DeepSeek Web 工具。提供方不比较聊天路由的地址与自己的端点:即使官方路由经网关聊天,每次操作也把配置的密钥(默认 `DEEPSEEK_API_KEY`)发往配置的端点。
|
||||
路由只决定是否先检查账号 token 再检查 API 密钥,这发生在 `deepseek-account` 路由上;API 密钥在每条路由上都算数,因此只要配置了 DeepSeek 密钥,使用其他提供方模型的对话也会获得 DeepSeek Web 工具。在 YAML 中设置了单独字段的 Search 或 Fetch API 能力不看路由,恰在其自身密钥已配置时就绪。
|
||||
|
||||
只看凭据是否存在:不读取 API 密钥的值,也不访问任何端点,因此错误的密钥,或不提供原生 `web_search_20250305` 服务器工具的端点,仍会让该工具保持准入。未准入某个 Web 工具的步骤也会以 `UNKNOWN_TOOL` 拒绝对它的调用,因此模型重复先前的调用也到达不了任何提供方;在模型步骤之外发起的操作在运行时解析凭据,没有凭据时以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败。
|
||||
|
||||
@@ -24,7 +24,7 @@ web 能力 seam 的每个角色各负责逐步骤检查的一部分:
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**让 DeepSeek 提供方在自己的 assemble 监听器中移除 `web_search`。** seam 保持不变,但提供方因此要点名另一个包的工具、提示词段落与 PTC 传输工具;而且即使部署选择了另一个搜索成功的提供方(例如 Exa),它也会移除该工具。
|
||||
**让 DeepSeek 提供方在自己的 assemble 监听器中移除 `web_search`。** seam 保持不变,但提供方因此要点名另一个包的工具、提示词段落与 PTC 传输工具;而且即使部署选择了另一个搜索成功的提供方(例如插件注册的提供方),它也会移除该工具。
|
||||
|
||||
**扩展 `available()`。** 它是同步的且不接收请求,而这项检查要读取异步凭据存储,并取决于请求的路由。依赖路由的 `available()` 还会让提供方选择取决于对话。
|
||||
|
||||
@@ -36,9 +36,9 @@ web 能力 seam 的每个角色各负责逐步骤检查的一部分:
|
||||
|
||||
**只在 DeepSeek 账号路由上提供搜索。** 账号 token 只对官方端点签发,因此网关的密钥不可能到达 DeepSeek。但所有发行 profile 的默认路由都是 `deepseek-official`,搜索会因此默认关闭,连官方 API 密钥的情况也一并关闭。
|
||||
|
||||
**除非搜索有自己的密钥,否则不向其他提供方的对话提供这些工具。** 其他模型对话中的搜索会由 DeepSeek 密钥计费,但 DeepSeek 搜索本就是该密钥付费使用的服务之一,配置了它的用户也期望无论用哪个模型对话,搜索都能用。Web 搜索设置页把密钥存在 `apiKeyEnv` 指向的引用下,默认是聊天路由也在读取的 `DEEPSEEK_API_KEY`,因此在那里输入的密钥永远无法服务其他模型;再加一个开放这些路由的 `routes` 配置项,只会让用户多找一样东西。
|
||||
**除非搜索有自己的密钥,否则不向其他提供方的对话提供这些工具。** 其他模型对话中的搜索会由 DeepSeek 密钥计费,但 DeepSeek 搜索本就是该密钥付费使用的服务之一,配置了它的用户也期望无论用哪个模型对话,搜索都能用。Search 与 Fetch API 发送聊天路由自己的密钥,设置页把旧版 Messages 搜索的密钥存在 `apiKeyEnv` 指向的引用下,默认是聊天路由也在读取的 `DEEPSEEK_API_KEY`,因此撤下工具会让已配置的密钥无法服务其他模型;再加一个开放这些路由的 `routes` 配置项,只会让用户多找一样东西。
|
||||
|
||||
**比较官方聊天路由的端点与搜索端点。** 两个端点不同源时撤下工具或不发送密钥,能让网关的密钥远离 DeepSeek,但提供方得自己找出聊天路由的配置。取第一条激活的 `dsh-llm-deepseek-api-key` Loader 行时,如果组合隔离了多个实例,回答的可能是无关的实例,从而关掉正确的路由,或放过网关的密钥;只有路由的所有者知道哪个端点服务某个请求,而在 `dsh-llm` 加地址字段会让它经 Remote 方法 `listProviders()` 进入浏览器。这项检查还会让经代理或开发端点对话、而密钥能被官方搜索端点接受的用户失去搜索。不做这项检查时,网关用户的搜索会在配置的端点上失败。
|
||||
**比较官方聊天路由的端点与 Web 端点,二者不同时撤下工具。** 这能让网关的密钥远离 DeepSeek,但也会让经代理或开发端点对话、而密钥能被 Web 端点接受的用户失去网页工具。Search 与 Fetch API 改为从其所发送密钥的路由的 origin 推导端点,因此密钥与端点来自同一条路由;旧版 Messages 搜索保留自己的端点,网关用户的搜索会在那里失败。
|
||||
|
||||
**保持工具可见,在执行时失败。** 模型会把一次调用花在无法鉴权的搜索上,并且每次尝试都会重复失败。
|
||||
|
||||
@@ -46,6 +46,6 @@ web 能力 seam 的每个角色各负责逐步骤检查的一部分:
|
||||
|
||||
## Consequences
|
||||
|
||||
`dsh-web` 增加以下公共成员:两个提供方接口上可选的 `ready()`、`WebRuntime.searchReady()` 与 `WebRuntime.fetchReady()`。每个就绪方法都是其唯一消费方所需的最小查询,这正是[观测接口决策](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)允许状态查询回归的方式;它们既不报告可用性,也不报告选择失败。Exa、Perplexity 与 HTTP 抓取提供方没有实现 `ready()`,其行为不变;只要开关让 HTTP 抓取保持选中,`web_fetch` 就始终准入。未准入某个 Web 工具的步骤,其 PTC 程序绑定中也没有它。无法提供原生服务器工具的端点仍会让工具保持准入,并使每次调用失败。在路由不变时移除工具会改变声明的工具,并使提示词缓存失效一次;模型切换本身已经使缓存失效。其他提供方对话中的搜索与抓取由配置的 DeepSeek 密钥计费;官方路由经网关对话的部署会把网关的密钥发往配置的端点(默认是官方端点),除非该端点接受这个密钥,否则每次操作都会失败。DeepSeek Web 包不读取 Loader 的行,也不依赖任何 LLM 包。
|
||||
`dsh-web` 增加以下公共成员:两个提供方接口上可选的 `ready()`、`WebRuntime.searchReady()` 与 `WebRuntime.fetchReady()`。每个就绪方法都是其唯一消费方所需的最小查询,这正是[观测接口决策](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)允许状态查询回归的方式;它们既不报告可用性,也不报告选择失败。HTTP 抓取提供方没有实现 `ready()`,因此只要选中 HTTP 抓取,`web_fetch` 就始终准入。未准入某个 Web 工具的步骤,其 PTC 程序绑定中也没有它。无法提供原生服务器工具的端点仍会让工具保持准入,并使每次调用失败。在路由不变时移除工具会改变声明的工具,并使提示词缓存失效一次;模型切换本身已经使缓存失效。其他提供方对话中的搜索与抓取由配置的 DeepSeek 密钥计费。官方路由经网关对话的部署,对 Search 与 Fetch API 会把网关的密钥发往该网关 origin 加 `/v1/tools`,对旧版 Messages 搜索则发往配置的 Messages 端点(默认是官方端点),除非该端点接受这个密钥,否则每次搜索都会失败。Search 与 Fetch API 通过模型提供方目录读取 DeepSeek 模型路由的 Loader 条目。
|
||||
|
||||
单元测试分别固定每个角色:`packages/web/web/tests/web.spec.ts` 覆盖 `searchReady()` 与 `fetchReady()` 询问哪个提供方以及选择失败时的回答,`packages/web/web-search-deepseek/tests/deepseek.spec.ts` 与 `packages/web/web-deepseek-api/tests/readiness.spec.ts` 覆盖 `ready()` 在账号路由与其他路由上的每种凭据情况,`packages/web/tool-web/tests/tool-web.spec.ts` 通过真实的提示词、工具与执行器服务解析两个工具的步骤准入,包括在撤下该工具的步骤中调用它时以 `UNKNOWN_TOOL` 拒绝。在真实组合中,发行版 headless 无密钥冒烟测试与 `web-search-unconfigured` 录制会话快照的搜索密钥引用都未设置,二者固定了搜索工具的撤下行为。快照回放提供占位 DeepSeek 密钥,因此其工具列表不依赖运行环境;借助该密钥,`pi-ai-conversation-updates` 中 pi-ai 路由上的对话保留 `web_search`。
|
||||
单元测试分别固定每个角色:[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) 覆盖 `searchReady()` 与 `fetchReady()` 询问哪个提供方以及选择失败时的回答,[deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) 与 [credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) 覆盖 `ready()` 在账号路由与其他路由上的每种凭据情况,包括就绪判断与每次操作一致,[tool-web.spec.ts](../../../../packages/web/tool-web/tests/tool-web.spec.ts) 通过真实的提示词、工具与执行器服务解析两个工具的步骤准入,包括在撤下该工具的步骤中调用它时以 `UNKNOWN_TOOL` 拒绝。在真实组合中,发行版 headless 无密钥冒烟测试与 `web-search-unconfigured` 录制会话快照的搜索密钥引用都未设置,二者固定了搜索工具的撤下行为。快照回放提供占位 DeepSeek 密钥,因此其工具列表不依赖运行环境;借助该密钥,`pi-ai-conversation-updates` 中 pi-ai 路由上的对话保留 `web_search`。
|
||||
+6
-6
@@ -9,14 +9,14 @@
|
||||
en: 8112c1c059d1cf10
|
||||
zh: 852d23c56a93a555
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/decision:
|
||||
en: 90d74300f045bf6e
|
||||
zh: 088e88944478ca2a
|
||||
en: 3cf1a8f986cea871
|
||||
zh: 3105cf2608108ecd
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/deferred-naming-cleanup:
|
||||
en: 4c428f65ef611d07
|
||||
zh: 1fdc5467b0203d08
|
||||
en: 7f59c9d14a36e133
|
||||
zh: 2e7829c59b4fcd24
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered:
|
||||
en: 65af55b5775c9bc5
|
||||
zh: 2ea75b3fd33ac0c2
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences:
|
||||
en: e8e96b0354f1a3c6
|
||||
zh: ad1bfe3600e14ff1
|
||||
en: 08613c24e7c552ab
|
||||
zh: 6f2509d3e61cb2c7
|
||||
+3
-4
@@ -10,7 +10,7 @@ Moving a capability into the experimental directory changes its ownership and su
|
||||
|
||||
## Decision
|
||||
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of naming exceptions. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated exception is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of unprefixed experimental names; under the [unprefixed naming decision](2026-11-04-unprefixed-experimental-package-names.md), every new experimental package joins it. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated entry is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid Host row selectors. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
|
||||
@@ -20,10 +20,9 @@ Compatibility protection covers Web and Desktop profiles, including existing nat
|
||||
|
||||
| Retained identity | Unconstrained cleanup | Why it is deferred |
|
||||
|---|---|---|
|
||||
| Published names in the naming-exception map | Give every experimental package the `@deepseek-ai/dsh-experimental-` prefix | Existing dependencies, imports, module declarations, and profile files use the established names |
|
||||
| Existing badge, Ralph, and native-tool row IDs | Give optional contributions consistently distinct `optional-*` selectors | Existing user overrides use those IDs; the ownership move does not require a new selector |
|
||||
|
||||
These naming changes are deferred, not prerequisites for experimental classification. A later cleanup requires an explicit release and migration decision, an inventory of affected Web/Desktop configurations, and installed-profile upgrade evidence. Directory moves or pre-stable API status alone do not retire these exceptions. The package list stays in the typed policy rather than being copied into this note or package READMEs.
|
||||
These naming changes are deferred, not prerequisites for experimental classification. A later cleanup requires an explicit release and migration decision, an inventory of affected Web/Desktop configurations, and installed-profile upgrade evidence. Directory moves or pre-stable API status alone do not retire these row IDs. The package list stays in the typed policy rather than being copied into this note or package READMEs.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -35,6 +34,6 @@ These naming changes are deferred, not prerequisites for experimental classifica
|
||||
|
||||
## Consequences
|
||||
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
The repository carries a typed map of unprefixed experimental names and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
|
||||
The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope.
|
||||
+3
-4
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是命名例外唯一手工维护的清单。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的例外会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是不带前缀的实验包名唯一手工维护的清单;按照[不带前缀命名决策](2026-11-04-unprefixed-experimental-package-names.zh.md),每个新实验包都加入其中。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的条目会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
|
||||
既有徽章、Ralph 和原生委派行 ID 作为 Host 行选择器保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
|
||||
@@ -20,10 +20,9 @@ Status: implemented
|
||||
|
||||
| 保留的身份 | 不受兼容性约束时的清理 | 暂缓原因 |
|
||||
|---|---|---|
|
||||
| 命名例外映射中的已发布名称 | 为每个实验包使用 `@deepseek-ai/dsh-experimental-` 前缀 | 既有依赖、导入、模块声明和 profile 文件使用原有名称 |
|
||||
| 既有徽章、Ralph 和原生工具行 ID | 为可选贡献统一使用独立的 `optional-*` 选择器 | 既有用户覆盖使用这些 ID;归属迁移不要求新的选择器 |
|
||||
|
||||
这些命名变更被暂缓,不是实验分类的前提。后续清理需要明确的发布与迁移决策、受影响 Web/Desktop 配置清单,以及已安装 profile 的升级验证。仅目录迁移或公共 API 处于稳定前阶段,不足以删除这些例外。包清单保留在带类型的策略中,不复制到本记录或各包 README。
|
||||
这些命名变更被暂缓,不是实验分类的前提。后续清理需要明确的发布与迁移决策、受影响 Web/Desktop 配置清单,以及已安装 profile 的升级验证。仅目录迁移或公共 API 处于稳定前阶段,不足以删除这些行 ID。包清单保留在带类型的策略中,不复制到本记录或各包 README。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -35,6 +34,6 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
仓库维护带类型的不带前缀实验包名映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
|
||||
[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 386864a282e5ad58
|
||||
zh: a4ea75ec70eb7778
|
||||
/agent-note-shared-native-cua-process-with-agent-scoped-state/decision:
|
||||
en: 11bab6870c3d802f
|
||||
zh: a22114f2217a3a41
|
||||
en: 60ec2276ed0bff33
|
||||
zh: 5fb48cc787194254
|
||||
/agent-note-shared-native-cua-process-with-agent-scoped-state/alternatives-considered:
|
||||
en: 883f71387f599160
|
||||
zh: 4b5f1450be799397
|
||||
|
||||
@@ -12,7 +12,7 @@ On macOS 26.6.2, replayd derives capture-client identity from the executable pat
|
||||
|
||||
One user shares a native service per executable content digest across Agents and Hosts. A version lock elects the service; an additional canonical-executable-path lock prevents different builds published at one path from running together. Locks remain held until process exit. The managed startup command hands service lifetime to its client connections, and the last connection's completed cleanup ends the service. The diagnostic stdin/stdout entry proxies to this same service.
|
||||
|
||||
The service runs as a signed application launched through LaunchServices. Directly spawning the executable inherits its launcher’s TCC responsibility, so it cannot give the helper independent system permissions. A launch lock serializes discovery and startup. Complete signed applications use immutable content-addressed directories: replacing an executable used by a live process would violate both path-lock ownership and code-signature integrity. Ad-hoc builds can require fresh grants when their code hash changes; a stable bundle identifier alone does not preserve those grants. Installed packages retain their distributed signatures.
|
||||
The service runs as a signed application launched through LaunchServices. Directly spawning the executable inherits its launcher’s TCC responsibility, so it cannot give the helper independent system permissions. A launch lock serializes discovery and startup. Complete signed applications use immutable content-addressed directories: replacing an executable used by a live process would violate both path-lock ownership and code-signature integrity. Ad-hoc builds can require fresh grants when their code hash changes; a stable bundle identifier alone does not preserve those grants. Source checkouts can keep grants with the [development signing identity](2026-10-29-cua-dev-signing-identity.md). Installed packages retain their distributed signatures.
|
||||
|
||||
Each Agent connection owns its application handles, AX snapshots, limits and cancellation state. An app handle grants no exclusive use of the application. A FIFO orders complete desktop operations, including clipboard transactions, while response writes are independent. Agent disposal retires only its connection. The [object API decision](2026-09-17-experimental-cua-object-api.md) retains evaluator independence and coordinate semantics; the [consent decision](2026-09-26-cua-application-consent.md) retains application authorization.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
同一用户的多个 Agent 和 Host 按可执行文件内容摘要共享原生服务。版本锁选出服务实例,规范化可执行路径上的另一把锁阻止发布在同一路径的不同构建同时运行。进程退出才释放锁。受管启动命令将服务生命期交给客户端连接,最后一条连接清理完成后服务退出。诊断用 stdin/stdout 入口也代理到同一个服务。
|
||||
|
||||
服务通过 LaunchServices 以已签名应用运行。直接启动可执行文件会继承启动器的 TCC 责任,无法让 helper 独立取得系统权限。启动锁串行处理发现与启动。完整签名应用保存在不可变的内容寻址目录中,替换运行中进程的可执行文件会破坏路径锁的归属与代码签名完整性。临时签名构建的代码哈希变化后可能需要重新授权,固定 Bundle ID 不能保证保留授权。安装版保留分发时的签名。
|
||||
服务通过 LaunchServices 以已签名应用运行。直接启动可执行文件会继承启动器的 TCC 责任,无法让 helper 独立取得系统权限。启动锁串行处理发现与启动。完整签名应用保存在不可变的内容寻址目录中,替换运行中进程的可执行文件会破坏路径锁的归属与代码签名完整性。临时签名构建的代码哈希变化后可能需要重新授权,固定 Bundle ID 不能保证保留授权。源码检出可以用[开发签名身份](2026-10-29-cua-dev-signing-identity.zh.md)保留授权。安装版保留分发时的签名。
|
||||
|
||||
每条 Agent 连接独立持有应用句柄、AX 快照、资源限制和取消状态。持有 app 句柄不代表独占应用。FIFO 队列串行执行包含剪贴板事务的完整桌面操作,响应写入独立进行。Agent 销毁只释放自己的连接。[对象接口决策](2026-09-17-experimental-cua-object-api.zh.md)继续负责执行器独立性及坐标语义,[授权决策](2026-09-26-cua-application-consent.zh.md)继续负责应用许可。
|
||||
|
||||
|
||||
+13
-13
@@ -2,18 +2,18 @@
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-13-team-identity-runtime-context.md
|
||||
/agent-note-team-identity-in-required-runtime-context:
|
||||
en: 8c88759320ae8481
|
||||
zh: 4068b18723907eb9
|
||||
/agent-note-team-identity-in-required-runtime-context/problem:
|
||||
en: c9a81cb731ddf7b2
|
||||
zh: 428c0e6f8937d7c5
|
||||
/agent-note-team-identity-in-required-runtime-context/decision:
|
||||
en: 6c73479973c1e378
|
||||
zh: 98677c77f77f8d81
|
||||
/agent-note-team-identity-in-required-runtime-context/alternatives-considered:
|
||||
/agent-note-subagent-identity-in-required-runtime-context:
|
||||
en: 3da6e33c5dc2c917
|
||||
zh: 9c8d4542cda63fc4
|
||||
/agent-note-subagent-identity-in-required-runtime-context/problem:
|
||||
en: f944254565541454
|
||||
zh: 27fa9f67c671dec7
|
||||
/agent-note-subagent-identity-in-required-runtime-context/decision:
|
||||
en: 85100936f97a139c
|
||||
zh: 614081943944e28b
|
||||
/agent-note-subagent-identity-in-required-runtime-context/alternatives-considered:
|
||||
en: 774566ffbf9e9db8
|
||||
zh: ea761d876af462f2
|
||||
/agent-note-team-identity-in-required-runtime-context/consequences:
|
||||
en: 18d1aa67936f1227
|
||||
zh: 7e06c1f51553ecdd
|
||||
/agent-note-subagent-identity-in-required-runtime-context/consequences:
|
||||
en: 2c01a24cd8cc4556
|
||||
zh: 37b5d1ca1173318a
|
||||
@@ -1,20 +1,22 @@
|
||||
# Agent Note: Team identity in required runtime context
|
||||
# Agent Note: Subagent identity in required runtime context
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-13-team-identity-runtime-context.zh.md)
|
||||
|
||||
[Catalog names](2026-11-02-subagent-catalog-names.md) supersede the Session-id identity text below: agents see their own, direct parent, and root names. The runtime-context retention decision remains active.
|
||||
|
||||
## Problem
|
||||
|
||||
A teammate identity embedded in its initial task depends on ordinary history retention. Compaction can remove it, and an ordinary fork from that teammate can retain the old role even though the fork is an independent Lead. Identity must reflect current membership while keeping shared system instructions uniform.
|
||||
A teammate identity embedded in its initial task depends on ordinary history retention. Compaction can remove it, and an ordinary fork from that teammate can retain the old role even though the fork is an independent Lead. Identity must reflect the current subagent tree while keeping shared system instructions uniform.
|
||||
|
||||
## Decision
|
||||
|
||||
`tool-agent-team` contributes `team:identity` through the Agent-scoped `systemPrompt.context()` registry with `required: true`. Its provider reads the existing Team membership and renders the member's role, name, and addressing instructions. Optional-context suppression cannot remove it. Fixed collaboration policy stays in the system prompt; roster and task state remain tool queries. Initial tasks contain only their supplied content.
|
||||
`tool-subagent-control` contributes required `subagent:identity` through `systemPrompt.context()` in ordinary subagent and Team compositions. It derives identity from `ctx.subagents.rootOf(agent)`: the root sees its own Session id, and descendants see their own, direct parent, and root ids. An unavailable root is reported explicitly. Optional-context suppression and Team mode do not remove identity. Agents receive messaging and discovery guidance only while the root is available and each corresponding standard tool is admitted. Team task policy stays in the shared system prompt; task state remains a tool query.
|
||||
|
||||
The loop owns logging, retained-snapshot comparison, and re-admission after compaction. A changed role appends the current full runtime-context snapshot after inherited history; unchanged retained context adds no message. No Team-specific retention state, event type, or log migration is introduced. Existing recorded reminders remain historical messages.
|
||||
|
||||
This supersedes only the initial-task identity decision in [Agent Teams](../feature/2026-08-05-agent-teams.md#team-identity); its roster, mailbox, and task decisions remain active.
|
||||
This supersedes only the initial-task identity decision in [Agent Teams](../feature/2026-08-05-agent-teams.md#team-identity). [Task-board wait coordination](2026-10-19-team-task-board-inbox.md) keeps ordinary inbox waiting independent of Team-only model guidance.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -28,4 +30,4 @@ This supersedes only the initial-task identity decision in [Agent Teams](../feat
|
||||
|
||||
Members pay for identity within the full runtime-context snapshot. Other context changes repeat it, while stable retained context adds no new message. Forks preserve inherited history and append current identity; actual provider cache hits remain best-effort. Existing Team authorization remains in tool execution.
|
||||
|
||||
Verification covers fresh and fork teammates, an independent fork from a teammate, optional-context suppression, compaction restoration, cold resume, and registration disposal. The recorded `team-targets` scenario pins Lead context; the keyless headless Team workflow pins teammate context through the shipped profile.
|
||||
Verification covers fresh and fork children, an independent Session fork, optional-context suppression, compaction restoration, unavailable roots, cold resume, and registration disposal. Recorded ordinary subagent and Team workflows pin identity through their shipped compositions; delegated tasks retain final-result return guidance.
|
||||
+7
-5
@@ -1,20 +1,22 @@
|
||||
# Agent Note: 必需 runtime context 中的 Team 身份
|
||||
# Agent Note: 必需 runtime context 中的 subagent 身份
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-13-team-identity-runtime-context.md) | 中文
|
||||
|
||||
[目录名称](2026-11-02-subagent-catalog-names.zh.md)取代下文以 Session id 表述身份的文本:agent 看到自身、直接父级和根的名称。runtime context 保留决策仍然有效。
|
||||
|
||||
## Problem
|
||||
|
||||
嵌入初始任务的 teammate 身份依赖普通历史保留。压缩可能移除它;从 teammate 创建的普通 fork 即使已成为独立 Lead,也可能保留旧角色。身份必须反映当前 membership,同时保持共享系统指令一致。
|
||||
嵌入初始任务的 teammate 身份依赖普通历史保留。压缩可能移除它;从 teammate 创建的普通 fork 即使已成为独立 Lead,也可能保留旧角色。身份必须反映当前 subagent 树,同时保持共享系统指令一致。
|
||||
|
||||
## Decision
|
||||
|
||||
`tool-agent-team` 通过 Agent 作用域的 `systemPrompt.context()` 注册 `team:identity`,并设置 `required: true`。提供方读取现有 Team membership,渲染成员角色、名字与寻址说明。关闭可选 context 不能移除它。固定协作策略保留在 system prompt;roster 和 task 状态仍通过工具查询。初始任务只包含传入的正文。
|
||||
`tool-subagent-control` 在普通 subagent 和 Team 组合中通过 `systemPrompt.context()` 提供必需的 `subagent:identity`。它从 `ctx.subagents.rootOf(agent)` 派生身份:根看到自身 Session id,后代看到自身、直接父级和根 id。根不可用时会明确报告。可选上下文抑制和 Team 模式都不会移除身份。只有根可用时,agent 才会分别根据对应标准工具的获准情况收到通信与发现指导。Team 任务策略保留在共享系统提示中;任务状态仍通过工具查询。
|
||||
|
||||
Loop 负责日志记录、保留快照比较和压缩后的重新接纳。角色变化会在继承历史之后追加当前完整 runtime-context 快照;保留的 context 未变化时不追加消息。不引入 Team 专属的保留状态、事件类型或日志迁移。已有身份提醒仍作为历史消息保留。
|
||||
|
||||
本决策仅取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md#team-identity) 中的初始任务身份决策;其 roster、mailbox 和 task 决策仍然有效。
|
||||
这仅取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md#team-identity) 中将身份放入初始任务的决策。[任务板等待协调](2026-10-19-team-task-board-inbox.zh.md)使普通收件箱等待独立于 Team 专用模型指导。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -28,4 +30,4 @@ Loop 负责日志记录、保留快照比较和压缩后的重新接纳。角色
|
||||
|
||||
成员承担完整 runtime-context 快照中的身份 token 成本。其他 context 变化会重复身份,保留的 context 稳定时则不追加消息。Fork 保留继承历史并追加当前身份;提供方实际缓存命中仍为尽力而为。现有 Team 授权仍在工具执行时落实。
|
||||
|
||||
验证覆盖 fresh 和 fork teammate、从 teammate 创建的独立 fork、关闭可选 context、压缩后恢复、冷恢复和注册释放。录制的 `team-targets` 场景固定 Lead context;无密钥 headless Team 工作流通过内置 profile 固定 teammate context。
|
||||
验证覆盖新建和 fork 子级、独立 Session fork、可选上下文抑制、压缩恢复、不可用根、冷恢复和注册释放。录制的普通 subagent 和 Team 工作流通过各自的分发组合固定身份行为;委派任务保留最终结果返回指导。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-15-agent-teams-as-subagents.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-15-agent-teams-as-subagents.md
|
||||
/agent-note-agent-teams-teammates-are-subagents:
|
||||
en: f49f9e7ef588e612
|
||||
zh: 7da804056221af51
|
||||
/agent-note-agent-teams-teammates-are-subagents/problem:
|
||||
en: ca2848e3bbc3a009
|
||||
zh: 0551e32ff6d187d0
|
||||
/agent-note-agent-teams-teammates-are-subagents/decision:
|
||||
en: 2c29c42cc02a9a05
|
||||
zh: bc542e3a27cfd03d
|
||||
/agent-note-agent-teams-teammates-are-subagents/alternatives-considered:
|
||||
en: ef0cf648ff54784f
|
||||
zh: 2a0b3745a54e7072
|
||||
/agent-note-agent-teams-teammates-are-subagents/consequences:
|
||||
en: 3ff718671be076fa
|
||||
zh: 38a367077ce95d52
|
||||
/agent-note-agent-teams-teammates-are-subagents/testing:
|
||||
en: a0ec8546a783bfd9
|
||||
zh: 8920b46970e3a2de
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Agent Teams teammates are subagents
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-15-agent-teams-as-subagents.zh.md)
|
||||
|
||||
The adjacency and parent-lifetime restrictions are superseded by [tree-wide communication](2026-10-16-tree-wide-agent-communication.md). [Task-board inbox coordination](2026-10-19-team-task-board-inbox.md) supersedes the separate roster and Team-owned wait; the single subagent identity decision remains active. [Catalog names](2026-11-02-subagent-catalog-names.md) supersede the statement that creation accepts no member name and the rejection of unique names below.
|
||||
|
||||
## Problem
|
||||
|
||||
Teammates already use the subagent runtime for creation, Session identity, catalog membership, capacity, cold resume, settlement, and interruption. A separate Team roster duplicates identity, provisioning recovery, and teardown. Team-scoped messaging and control tools shadow the standard subagent tools, forcing profiles to disable ordinary delegation and making fork tool schemas diverge from their parent.
|
||||
|
||||
## Decision
|
||||
|
||||
Use one subagent identity: the Session id. The existing `description` tool argument supplies the catalog label and display text; descriptions may repeat and never address an agent. Creation accepts no member name, reserves no names, and adds no catalog payload version.
|
||||
|
||||
[Tree-wide communication](2026-10-16-tree-wide-agent-communication.md) authorizes an exact live sender within one root tree and preserves the actual sender’s id. Interruption retains ancestor authority and id addressing.
|
||||
|
||||
Every root Agent and its descendants form the Team identified by the root Session id. Team creates no agents and contributes task tools and task policy. Ordinary subagent controls supply required identity, discovery, messaging, interruption, and inbox waiting. Parent and fork children receive identical tool schemas and shared policies.
|
||||
|
||||
Task ownership uses `ownerId`; root reassignment accepts itself or any reachable continuable descendant. Historical roster records remain decode-only. Team persists task changes without ending inbox waits or starting idle turns; [inbox-only waiting](../simplification/2026-10-29-inbox-only-agent-wait.md) owns wake behavior. Historical `team/message/*` records remain readable but are never delivered or acknowledged.
|
||||
|
||||
This supersedes the roster, provisioning, recovery, and messaging decisions in [Agent Teams](../feature/2026-08-05-agent-teams.md) and the [Team direct inbox decision](../../archived/simplification/2026-09-26-team-direct-inbox.md). The task-board and shared-checkout decisions remain active. [Adjacent Agent messaging](2026-08-27-adjacent-agent-steer-messaging.md) includes sibling routing, while [required runtime-context identity](2026-10-13-team-identity-runtime-context.md) owns the role statement. The [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/agent-team-subagents/guide.md) lists removed tools, methods, and configuration.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Separate Team authority and tools.** This retains a second roster and recovery lifecycle for Agents already owned by the subagent runtime, plus same-name tools and a private delivery adapter. The shared parent relation supplies the required authority.
|
||||
|
||||
**Unique names as sibling-routing opt-in.** Names add validation, reservation, persistence, and ambiguous resolution beside existing Session ids. Descriptions already explain responsibilities; stable ids distinguish repeated descriptions without another identity system.
|
||||
|
||||
**Pluggable address books.** A registry would support groups spanning unrelated parents but add another authorization input. No current consumer needs this; revisit when collaboration must cross parent trees.
|
||||
|
||||
## Consequences
|
||||
|
||||
Tree members discover and message one another through ordinary controls. Team retains task ownership, dependencies, and task policy. Subagent capacity and lifecycle apply; failed starts create no Team roster row. Existing durable task generations remain unchanged.
|
||||
|
||||
Descendants at any depth share the board and can be task owners. Historical members grant no authority. Separate working directories remain explicit caller choices; cross-process task coordination is unsupported.
|
||||
|
||||
## Testing
|
||||
|
||||
Verification covers tree delivery and cold resume with actual sender attribution, task CAS and owner discovery, historical decoding, inbox wait isolation, and registration disposal. Recorded sessions cover Team policy and identity, plus ordinary subagent return guidance.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Agent Teams 队友是子代理
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-15-agent-teams-as-subagents.md) | 中文
|
||||
|
||||
邻接和父级生命周期限制由[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)取代。[任务板收件箱协调](2026-10-19-team-task-board-inbox.zh.md)取代独立名册和 Team 所有的等待;单一 subagent 身份决策仍有效。[目录名称](2026-11-02-subagent-catalog-names.zh.md)取代下文“创建不接收成员名称”的表述及对唯一名称的否决。
|
||||
|
||||
## Problem
|
||||
|
||||
队友已经使用子代理运行时完成创建、Session 身份、目录成员关系、容量、冷恢复、结算和中断。独立 Team 成员列表重复了身份、创建恢复和释放职责。Team 作用域内的消息及控制工具覆盖标准子代理工具,迫使 profile 禁用普通委派,并使分叉工具模式偏离父级。
|
||||
|
||||
## Decision
|
||||
|
||||
统一使用子代理的 Session id 作为身份。现有 `description` 工具参数提供目录标签和展示文本;描述可以重复,不用于寻址代理。创建不接收成员名称、不预留名称,也不增加目录载荷版本。
|
||||
|
||||
[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)在同一根树内授权当前在线发送方,并保留实际发送方 id。中断保留祖先权限和 id 寻址。
|
||||
|
||||
每个根 Agent 及其后代组成以根 Session id 标识的 Team。Team 不创建 agent,提供任务工具和任务策略。普通 subagent 控制工具提供必需身份、发现、通信、中断和收件箱等待。父级和 fork 子级接收相同工具 schema 和共享策略。
|
||||
|
||||
任务归属使用 `ownerId`;根重新分配可指定自身或任意可达的可继续执行后代。历史名册记录仅供解码。Team 持久化任务变更,不结束收件箱等待,也不启动空闲轮次;[仅等待收件箱](../simplification/2026-10-29-inbox-only-agent-wait.zh.md)负责唤醒行为。历史 `team/message/*` 记录仍可读取,但绝不再投递或确认。
|
||||
|
||||
本决策取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的成员列表、创建、恢复和消息决策,以及 [Team 直接收件箱决策](../../archived/simplification/2026-09-26-team-direct-inbox.md)。任务板和共享工作树决策仍有效。[相邻 Agent 消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)纳入同级路由,[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)负责角色声明。[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/agent-team-subagents/guide.zh.md)列出移除的工具、方法和配置。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**独立 Team 权限和工具。** 这会为子代理运行时已拥有的 Agent 保留第二套成员列表与恢复生命周期,以及同名工具和私有投递适配器。共同父级关系已经提供所需权限。
|
||||
|
||||
**以唯一名称选择性启用同级路由。** 名称会在现有 Session id 之外增加校验、预留、持久化和歧义解析。描述已能说明职责;稳定 id 可以区分重复描述,无需另一套身份系统。
|
||||
|
||||
**可插拔地址簿。** 注册表可支持跨不同父级的组,但会增加另一种授权输入。当前没有消费者需要此能力;协作必须跨父级树时再考虑。
|
||||
|
||||
## Consequences
|
||||
|
||||
树内成员通过普通控制工具发现和联系彼此。Team 保留任务归属、依赖和任务策略。容量和生命周期遵循 subagent 规则;启动失败不会创建 Team 名册行。现有持久化任务代次保持不变。
|
||||
|
||||
任意深度后代共享任务板,均可成为任务所有者。历史成员不授予权限。独立工作目录仍由调用者显式选择;不支持跨进程任务协调。
|
||||
|
||||
## Testing
|
||||
|
||||
验证覆盖具有实际发送方归属的整树投递和冷恢复、任务 CAS 与所有者发现、历史解码、收件箱等待隔离和注册释放。录制会话覆盖 Team 策略和身份,以及普通 subagent 返回指导。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-16-tree-wide-agent-communication.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-16-tree-wide-agent-communication.md
|
||||
/agent-note-communication-follows-the-subagent-tree:
|
||||
en: 849d806b901e9f04
|
||||
zh: 649b2984f844d510
|
||||
/agent-note-communication-follows-the-subagent-tree/problem:
|
||||
en: c583c3cdb894e417
|
||||
zh: 6edc0354b79f4f26
|
||||
/agent-note-communication-follows-the-subagent-tree/decision:
|
||||
en: 767b4b4dfd84f72e
|
||||
zh: 36617ec0dab777ee
|
||||
/agent-note-communication-follows-the-subagent-tree/alternatives-considered:
|
||||
en: 470f9cf8c3e32af9
|
||||
zh: 73bbe29767c5d02a
|
||||
/agent-note-communication-follows-the-subagent-tree/consequences:
|
||||
en: 6d409833d762d473
|
||||
zh: 000cd93577ec6b34
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Communication follows the subagent tree
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-16-tree-wide-agent-communication.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A Team shares one task board across the root's entire subagent tree. Restricting discovery and messages to adjacent relatives forces deeper members to relay work through parents despite sharing tasks.
|
||||
|
||||
## Decision
|
||||
|
||||
`list_agents` starts at the caller's live root and lists that root and all reachable continuable descendants, including the caller. Full names remain relative to the root. The existing parent catalogs remain authoritative; no Team directory is stored separately.
|
||||
|
||||
`send_message` accepts another root or continuable member of the same tree. The sender must be the exact live root or a resident continuable subagent. Cross-branch delivery verifies the target descriptor and creation catalogs, then restores only the target under the Team root. Final inbox admission rechecks the sender and root. Cancellation or rejection before acceptance releases the new target instance; accepted input follows normal Agent persistence.
|
||||
|
||||
The existing Agent registry records runtime ownership by the Team root. Creation parent ids and catalogs preserve ancestry for discovery and interrupt authority. A member finishing or being released affects only its own instance; Team-root shutdown joins all startup and cleanup. This removes ancestor restoration, parent retention, and recursive disposal without adding a Team runtime registry. Saved target presets and permission events supply restoration configuration.
|
||||
|
||||
This supersedes the adjacency, discovery, and parent-lifetime restrictions in [adjacent messaging](2026-08-27-adjacent-agent-steer-messaging.md) and [subagent teammates](2026-10-15-agent-teams-as-subagents.md). Their shared inbox, scheduling, attribution, and Team reuse decisions remain active. Members address one another by the names that [catalog names](2026-11-02-subagent-catalog-names.md) define, or by Session id; ordinary Session forks lead independent Teams.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Relay through parents.** This preserves the narrower routing implementation but makes the creation hierarchy determine who can collaborate. Shared work requires direct communication across branches.
|
||||
|
||||
**Separate Team directory and delivery.** A second member list and message path would duplicate the existing catalogs, inbox, and restoration lifecycle. Tree membership supplies the required communication scope without another registry.
|
||||
|
||||
## Consequences
|
||||
|
||||
Discovery output grows with the whole tree. Cold delivery requires one capacity slot and readable creation history. Intermediate creators may be offline. Completion notices reach only online creators, so work that requires descendant results must explicitly wait. Listing is a snapshot and does not promise that a later send succeeds. External and one-shot executions cannot receive follow-ups.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 通信范围由子代理树决定
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-16-tree-wide-agent-communication.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
一个 Team 在根的整棵子代理树中共享任务板。把发现和消息限定在相邻成员之间,会迫使深层成员在共享任务的同时,仍通过父级转发工作。
|
||||
|
||||
## Decision
|
||||
|
||||
`list_agents` 从调用方的在线根开始,列出该根与所有可达的可继续后代,包括调用方。完整名称始终相对根计算。现有父级目录仍是权威来源;不单独存储 Team 目录。
|
||||
|
||||
`send_message` 接受同一棵树中的其他根或可继续成员。发送者必须是确切在线根或驻留的可继续子代理。跨分支投递验证目标描述符和创建目录后,仅在 Team 根下恢复目标。最终收件箱准入重新检查发送者与根。接受前的取消或拒绝释放新建目标实例;已接受输入遵循普通 Agent 持久化。
|
||||
|
||||
现有 Agent 注册表记录 Team 根对运行时实例的所有权。创建父级 id 和目录保留用于发现及中断授权的祖先关系。成员完成或释放只影响自身实例;关闭 Team 根会等待全部启动和清理。这删除了祖先恢复、父级保留和递归释放,无需增加 Team 运行时注册表。目标保存的 preset 和权限事件提供恢复配置。
|
||||
|
||||
本决策替代[相邻消息](2026-08-27-adjacent-agent-steer-messaging.zh.md)与[子代理队友](2026-10-15-agent-teams-as-subagents.zh.md)中的邻接、发现及父子生命周期限制。它们的共享收件箱、调度、发送者归属和 Team 复用决策仍有效。成员使用[目录名称](2026-11-02-subagent-catalog-names.zh.md)定义的名称或 Session id 互相寻址;普通 Session 分叉领导独立 Team。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**通过父级转发。** 这保留了更窄的路由实现,却让创建层级决定谁能协作。共享工作需要跨分支直接通信。
|
||||
|
||||
**单独的 Team 目录与投递。** 第二份成员列表和消息路径会重复现有目录、收件箱与恢复生命周期。树成员身份已经提供所需通信范围,无需另一个注册表。
|
||||
|
||||
## Consequences
|
||||
|
||||
发现输出随整棵树增长。冷投递需要一个容量名额和可读的创建历史。中间创建者可以离线。完成通知仅投递给在线创建者,因此依赖后代结果的工作必须显式等待。列表是快照,不保证后续发送成功。外部执行和一次性执行不能接收后续消息。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-19-team-task-board-inbox.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-19-team-task-board-inbox.md
|
||||
/agent-note-team-task-boards-share-ordinary-subagent-waits:
|
||||
en: 7f19734715f5d50f
|
||||
zh: 95af09c9d855d1af
|
||||
/agent-note-team-task-boards-share-ordinary-subagent-waits/problem:
|
||||
en: 6fdc766a348d8659
|
||||
zh: 8a84748e147deed5
|
||||
/agent-note-team-task-boards-share-ordinary-subagent-waits/decision:
|
||||
en: 154da8a8a5146c89
|
||||
zh: 76a7c921153d238d
|
||||
/agent-note-team-task-boards-share-ordinary-subagent-waits/alternatives-considered:
|
||||
en: eed6f6d4fa0ccd7b
|
||||
zh: 61271057d6e5d019
|
||||
/agent-note-team-task-boards-share-ordinary-subagent-waits/consequences:
|
||||
en: cc250eb21dbb45a1
|
||||
zh: 9d03d1eb53075fb6
|
||||
@@ -0,0 +1,31 @@
|
||||
# Agent Note: Team task boards share ordinary subagent waits
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-19-team-task-board-inbox.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The subagent tree already supplies identity, discovery, routing, and lifecycle. A second Team roster can grant assignment authority from stale history and duplicates member navigation. Team-wide status waits observe unrelated activity and miss input already pending for the caller. Persisting task-board wakeups as messages adds unread state and message schemas even though the task journal already owns the state to inspect.
|
||||
|
||||
## Decision
|
||||
|
||||
The subagent tree is the Team. Ordinary controls own required identity, communication, discovery, interruption, and `wait_agent`, independently of the optional task board. Team tools own task policy, without adding it to ordinary subagent compositions. Team owns task storage, dependencies, and compare-and-set commands. Assignment accepts the root or any discovered continuable descendant. Historical roster records grant no authority; the Team panel shows tasks and ordinary navigation owns members.
|
||||
|
||||
The wait-signal decision is superseded by [inbox-only Agent waits](../simplification/2026-10-29-inbox-only-agent-wait.md). Tree ownership, task durability, and historical non-authority remain unchanged.
|
||||
|
||||
Task state remains in the existing journal. Ordinary tool results record what the waiting model observes. Historical roster, mailbox, and task generations remain readable and unchanged. This partially supersedes the roster and wait decisions in [Agent Teams](../feature/2026-08-05-agent-teams.md) and [subagent teammates](2026-10-15-agent-teams-as-subagents.md). Their task durability and single-identity rationale remain active; [required runtime identity](2026-10-13-team-identity-runtime-context.md) owns retention.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep Team membership and a separate wait tool.** This duplicates tree authority and wait lifecycle, allowing stale roster rows or unrelated activity to affect current work.
|
||||
|
||||
**Persist task-board notices in every inbox.** The task journal already preserves every revision. Durable notices add message types, unread state, coalescing, and delivery-failure recovery solely to interrupt a current wait.
|
||||
|
||||
**Poll Agent status or start every idle task owner.** Activity does not identify useful input, and task readiness does not authorize a new turn. Explicit messages continue idle work when needed.
|
||||
|
||||
## Consequences
|
||||
|
||||
Task completion, inbox availability, current waiting, and Agent lifetime remain separate. Task updates do not end waits; callers inspect current tasks when deciding what to do. The task journal retains committed revisions without storing redundant notifications in recipient Sessions.
|
||||
|
||||
Verification distinguishes pending inbox input from task commits, isolates callers, excludes next-turn input and status changes, and covers wait cleanup and task persistence. Assignment coverage retains arbitrary depth and historical non-authority.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Agent Note: Team 任务板复用普通 subagent 等待
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-19-team-task-board-inbox.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
subagent 树已经提供身份、发现、路由和生命周期。第二份 Team 名册可能根据过期历史授予分配权限,并重复成员导航。Team 范围状态等待会观察无关活动,却漏掉调用者已有的待处理输入。任务日志已经拥有待查询状态,将任务板唤醒持久化为消息仍会增加未读状态和消息 schema。
|
||||
|
||||
## 决策
|
||||
|
||||
subagent 树就是 Team。普通控制工具独立于可选任务板,负责必需身份、通信、发现、中断和 `wait_agent`。Team 工具负责任务策略,不向普通 subagent 组合添加该策略。Team 负责任务存储、依赖和比较并交换命令。分配接受根或任意已发现的可继续执行后代。历史名册不授予权限;Team 面板显示任务,普通导航负责成员。
|
||||
|
||||
等待信号决策已由[仅等待收件箱的 Agent 等待](../simplification/2026-10-29-inbox-only-agent-wait.zh.md)取代。树归属、任务持久性和历史记录不授予权限的决策保持不变。
|
||||
|
||||
任务状态保留在现有日志中。普通工具结果记录等待中的模型所观察到的结果。历史名册、邮箱和任务代次保持可读且不变。这部分取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 和 [subagent 队友](2026-10-15-agent-teams-as-subagents.zh.md) 中的名册与等待决策。其任务持久性和单一身份理由继续生效;[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)负责保留规则。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留 Team 成员关系与独立等待工具。** 这会重复树权限和等待生命周期,让过期名册行或无关活动影响当前工作。
|
||||
|
||||
**将任务板通知持久化到每个收件箱。** 任务日志已保留每个修订。持久通知仅为打断当前等待,就增加消息类型、未读状态、合并和投递失败恢复。
|
||||
|
||||
**轮询 Agent 状态或启动每个空闲任务所有者。** 活动状态不能标识有用输入,任务就绪也不授权新轮次。需要时通过显式消息继续空闲成员的工作。
|
||||
|
||||
## 后果
|
||||
|
||||
任务完成、收件箱可用性、当前等待和 Agent 生命周期保持独立。任务更新不会结束等待;调用者在决定下一步工作时查询当前任务。任务日志保留已提交修订,不在接收者 Session 中存储重复通知。
|
||||
|
||||
验证区分已有收件箱输入和任务提交,隔离调用者,排除下一轮输入及状态变化,并覆盖等待清理和任务持久性。分配验证保留任意深度和历史记录无权限场景。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-20-session-team-mode.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-20-session-team-mode.md
|
||||
/agent-note-team-availability-is-selected-per-session:
|
||||
en: 5f36d81bd414ea67
|
||||
zh: 71883b0fe2afd1b6
|
||||
/agent-note-team-availability-is-selected-per-session/problem:
|
||||
en: db7a81ab452fdd0f
|
||||
zh: a14fdcdb8ff4cd47
|
||||
/agent-note-team-availability-is-selected-per-session/decision:
|
||||
en: d1174db5157b91ee
|
||||
zh: ceb6dc5a89a0a8d6
|
||||
/agent-note-team-availability-is-selected-per-session/alternatives-considered:
|
||||
en: d50ed45e8f902281
|
||||
zh: c01d9be4b6e5df06
|
||||
/agent-note-team-availability-is-selected-per-session/consequences:
|
||||
en: b56a6a1c8ad1aa7a
|
||||
zh: 43bdd8a5bccce8bf
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: Team availability is selected per Session
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-20-session-team-mode.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A plugin-level Team switch requires users to change the shared profile before choosing collaboration for one conversation. It can disable another Session’s capability and leaves a second control beside the Session selection. Ordinary tasks still need their existing tool catalog, and disabling collaboration must not discard task history or children.
|
||||
|
||||
## Decision
|
||||
|
||||
The base bundle loads the Team service and task tools, and the Web bundle loads the Team UI. The packages belong to the subagent and client product groups. A saved root-Session selection gates task tools and Team guidance throughout the tree, and the shared board in the root conversation; new Sessions default off. Ordinary subagent creation, communication, and navigation remain available.
|
||||
|
||||
The model and reasoning menu owns the user’s selection. The root conversation’s Subagent panel displays members above shared tasks. Disabling Team keeps the journal and children; re-enabling restores the board. Historical task boards without a saved selection stay enabled. Profile loading removes the retired optional bundle selection, retaining row overrides.
|
||||
|
||||
Headless deployments may set `agent-team.defaultEnabled` to save an initial selection for a new root during startup creation. Saved selections take precedence, and resumed Sessions retain their mode even when no selection exists. Ordinary forks initialize independently; subagents share their live root’s selection. Creation identifies subagents by their Session origin because activation ownership is registered only after creation listeners settle. Task access still requires the live runtime owner.
|
||||
|
||||
This supersedes the explicit-composition choice in [Agent Teams](../feature/2026-08-05-agent-teams.md). Its durability, ownership, and lifecycle decisions remain active. [Optional bundles](../process/2026-09-15-shipped-optional-bundles.md) still governs other experimental features.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep both switches.** Capability installation remains a prerequisite that the conversation control cannot explain or resolve; users must manage shared profile state for a Session decision.
|
||||
|
||||
**Apply the deployment default on resume or at read time.** Changing a profile would change an existing conversation’s tools without a saved selection. Persisting the default once at creation keeps subsequent capability decisions reconstructable from the Session log.
|
||||
|
||||
**Hide the plugin card without loading Team.** New profiles would have no way to obtain the capability. Loading experimental packages in the default profile would violate product isolation; moving them to their owning product groups makes the delivered support explicit.
|
||||
|
||||
## Consequences
|
||||
|
||||
Default profiles carry the Team implementation while exposing no task tools until enabled. The removed package names require custom compositions to migrate; durable event names and task data remain unchanged. [The upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md) owns those steps.
|
||||
|
||||
Verification covers default startup, new-Session opt-in, independent roots, persisted selections, retained tasks, old profile migration, and absence of the plugin switch. Product-isolation checks retain an unrelated experimental plugin as their negative control.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: 按 Session 选择 Team 能力
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-20-session-team-mode.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
插件级 Team 开关要求用户先修改共享 profile,才能为一个会话选择协作。它可能关闭另一个 Session 的能力,并在会话选项之外留下第二个控件。普通任务仍需要原有工具目录,关闭协作也不能丢弃任务历史或子智能体。
|
||||
|
||||
## Decision
|
||||
|
||||
基础 bundle 加载 Team 服务与任务工具,Web bundle 加载 Team UI。相关包归属 subagent 与 client 产品组。根 Session 保存的选择控制整棵树的任务工具与 Team 指引,以及根会话中的共享任务板;新 Session 默认关闭。普通 subagent 创建、通信与导航继续可用。
|
||||
|
||||
模型与推理等级菜单承载用户选择。根会话的 Subagent 面板在成员下方显示共享任务。关闭 Team 保留日志与子智能体;重新开启恢复任务板。没有保存选择的历史任务板保持开启。profile 加载时移除已停用的可选 bundle 选择,保留配置行覆盖。
|
||||
|
||||
无界面部署可设置 `agent-team.defaultEnabled`,在新根会话的启动创建阶段保存初始选择。已保存的选择优先,恢复的 Session 即使没有保存选择也保留原模式。普通分叉独立初始化;子智能体共享当前运行时根的选择。创建阶段通过 Session origin 识别子智能体,因为激活归属在创建监听器结束后才登记。任务访问仍要求当前运行时归属有效。
|
||||
|
||||
本决策替代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 中显式选择组合的部分;其持久性、归属与生命周期决策继续有效。[可选 bundle](../process/2026-09-15-shipped-optional-bundles.zh.md)仍约束其他实验性功能。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留两个开关。** 安装能力仍是会话控件无法解释或解决的前置条件;用户必须为 Session 决策管理共享 profile 状态。
|
||||
|
||||
**在恢复或读取时应用部署默认值。** 修改 profile 将在没有保存选择的情况下改变现有会话的工具。在创建时一次性持久化默认值,可让后续能力选择从 Session 日志重建。
|
||||
|
||||
**隐藏插件卡片但不加载 Team。** 新 profile 将无法获得该能力。默认 profile 加载实验性包会违反产品隔离;将相关包移入所属产品组可明确随产品提供的支持。
|
||||
|
||||
## Consequences
|
||||
|
||||
默认 profile 包含 Team 实现,但开启前不暴露任务工具。自定义组合必须迁移已移除的包名;持久事件名称与任务数据保持不变。[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md)负责具体步骤。
|
||||
|
||||
验证覆盖默认启动、新 Session 手动开启、独立根、选择持久化、任务保留、旧 profile 迁移和插件开关移除。产品隔离检查使用另一个无关实验性插件作为负对照。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-22-subagent-target-user-controls.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-22-subagent-target-user-controls.md
|
||||
/agent-note-user-controls-address-the-target-subagent:
|
||||
en: d7dcf54ddb311881
|
||||
zh: 5f388447737c5414
|
||||
/agent-note-user-controls-address-the-target-subagent/problem:
|
||||
en: 962c75771235903c
|
||||
zh: 203b651c0830310e
|
||||
/agent-note-user-controls-address-the-target-subagent/decision:
|
||||
en: 13a2617e28445744
|
||||
zh: 75d193ef7d8c7804
|
||||
/agent-note-user-controls-address-the-target-subagent/alternatives-considered:
|
||||
en: 447adb24280bf2bb
|
||||
zh: 08d95a93b14aa5c7
|
||||
/agent-note-user-controls-address-the-target-subagent/consequences:
|
||||
en: 98e784f55775247d
|
||||
zh: 1c62d2ebc71b658f
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: User controls address the target subagent
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-22-subagent-target-user-controls.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A browser knows the subagent it is displaying, but requiring its direct creator and a continuable marker makes every control caller repeat history metadata. A caller-supplied parent id checks address consistency without establishing the caller's identity or permission. Calls that resolve only the global access resource also cannot express a grant on one target Session.
|
||||
|
||||
## Decision
|
||||
|
||||
Browser prompts and `interruptByUser` address only the target child Session. Each Remote method explicitly declares that Session as its access resource; the Gateway checks the invocation's principal before attachment admission, restoration, or cancellation. A global or parent Session grant does not imply a child Session grant. Policies decide whether to grant both actions, either action, or inherited access. Same-process user interruption trusts its caller and does not accept a wire-supplied principal.
|
||||
|
||||
Prompt delivery derives the live root from the resident Activation or verified persisted creation edges. Only the cold target resumes; its creator can remain offline. Shared delivery retains exact live root checks, shutdown admission, cancellation, capacity, and final inbox acceptance. A root Session is not a target of the browser subagent prompt endpoint. User interruption addresses a resident Activation without persistence reads or restoration; an absent target remains a no-op, and interruption preserves parked input.
|
||||
|
||||
Model messages retain the exact live sender and same-tree rule from [tree-wide communication](2026-10-16-tree-wide-agent-communication.md). Model interruption retains its stricter ancestor rule. Creation parents still determine catalog edges, history-address consistency, ancestry, and completion recipients; they are not browser control credentials.
|
||||
|
||||
This supersedes the direct-parent addressing requirement for human continuation in [continuable subagents](../feature/2026-07-28-continuable-subagent-conversations.md). That record retains its inbox and durable continuation rationale; tree-wide communication owns its superseded model adjacency and runtime ownership restrictions. No Session format changes.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the parent check as authorization.** The client supplies the parent id and can obtain it from history. Matching that id does not authorize the principal, and requiring it couples controls to discovery metadata.
|
||||
|
||||
**Require a root id from the browser.** The server already derives the runtime owner from the target. Another supplied id adds a consistency check without granting permission; Agent senders still need an independently resolved root to enforce same-tree messaging.
|
||||
|
||||
**Unify user and Agent interruption permissions.** A user with a target grant can stop that target, while an Agent may stop only its descendants. Same-tree collaboration does not grant members control over each other's execution.
|
||||
|
||||
## Consequences
|
||||
|
||||
Remote callers update their prompt fields, interrupt endpoint, and non-owner policy actions and resources; the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.md) owns the migration. History URLs retain parent ids for consistency checks. Cold host prompts may read creation ancestry twice, once to find the live root and again during restoration; no verified-ancestry cache is retained across asynchronous work.
|
||||
|
||||
Gateway tests cover target grants, global-only and parent-only refusal, separate send and interrupt actions, owner access, and refusal before side effects. Continuation tests cover nested and cold targets with online or offline creators, unavailable roots, and rejection of a root as a subagent target. The Web interrupt scenario exercises the renamed endpoint and parked-input behavior through the shipped composition.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Agent Note: 用户控制按目标子代理寻址
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-22-subagent-target-user-controls.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
浏览器知道当前展示的子代理,但要求提供直接创建者和 continuable 标记,使每个控制调用方都要重复历史元数据。调用方提供的父级 id 只能检查地址一致性,不能确定调用者身份或权限。仅解析出 global 访问资源的调用也无法表达对单个目标 Session 的授权。
|
||||
|
||||
## 决策
|
||||
|
||||
浏览器提示词和 `interruptByUser` 仅按目标子 Session 寻址。每个 Remote 方法显式声明该 Session 为访问资源;Gateway 在附件接纳、恢复或取消之前检查调用的 principal。global 或父 Session 授权不意味着子 Session 授权。策略决定授予两个操作、其中一个操作,或继承访问权。同进程用户中断信任其调用方,不接受从线上传入的 principal。
|
||||
|
||||
提示词投递从驻留 Activation 或经过验证的持久化创建关系中解析在线根。仅恢复冷目标,创建者可以保持离线。共享投递保留精确在线根检查、关闭准入、取消、容量和最终 inbox 接纳。根 Session 不是浏览器子代理提示词端点的目标。用户中断直接寻址驻留 Activation,不读取持久化数据,也不恢复目标;目标缺席仍为无操作,中断保留停放的输入。
|
||||
|
||||
模型消息保留[整树通信](2026-10-16-tree-wide-agent-communication.zh.md)中的精确在线发送者和同树规则。模型中断保留更严格的祖先规则。创建父级仍决定目录关系、历史地址一致性、祖先关系和完成通知接收者;它们不是浏览器控制凭证。
|
||||
|
||||
本决策取代[可继续的 subagent](../feature/2026-07-28-continuable-subagent-conversations.zh.md)对人工继续执行要求直接父级地址的部分。旧记录保留 inbox 和持久化继续执行的理由;整树通信决策拥有其中已取代的模型相邻关系和运行时所有权限制。Session 格式不变。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留父级检查作为授权。** 客户端提供父级 id,也能从历史中取得它。匹配该 id 不能授权 principal,还使控制依赖于发现元数据。
|
||||
|
||||
**要求浏览器提供根 id。** 服务端已经能从目标解析运行时所有者。再提供一个 id 只会增加一致性检查,不会授予权限;Agent 发送者仍需要独立解析的根来约束同树消息。
|
||||
|
||||
**统一用户和 Agent 的中断权限。** 用户获得目标授权后可以停止该目标,而 Agent 只能停止自己的后代。同树协作不赋予成员控制彼此执行的权限。
|
||||
|
||||
## 影响
|
||||
|
||||
Remote 调用方需要更新提示词字段、中断端点,以及非 owner 策略的操作和资源;迁移步骤由[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.zh.md)拥有。历史 URL 保留父级 id 用于一致性检查。冷目标的宿主提示词可能读取两次创建祖先:先查找在线根,再在恢复期间验证;不跨异步操作保留已验证祖先缓存。
|
||||
|
||||
Gateway 测试覆盖目标授权、仅 global 和仅父级授权的拒绝、独立的发送和中断操作、owner 访问,以及副作用前拒绝。继续执行测试覆盖创建者在线或离线时的嵌套和冷目标、根不可用,以及拒绝把根当作子代理目标。Web 中断场景通过交付组合验证重命名后的端点和输入停放行为。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-22-subagent-tool-owned-communication.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-22-subagent-tool-owned-communication.md
|
||||
/agent-note-subagent-tools-own-communication-guidance:
|
||||
en: 0bd031aa81fbc18d
|
||||
zh: c530e980826698ed
|
||||
/agent-note-subagent-tools-own-communication-guidance/problem:
|
||||
en: ac391b569d2bc7e9
|
||||
zh: a95f613aa2b330b8
|
||||
/agent-note-subagent-tools-own-communication-guidance/decision:
|
||||
en: d83310e85ffcb3cf
|
||||
zh: eb1f069948399d93
|
||||
/agent-note-subagent-tools-own-communication-guidance/alternatives-considered:
|
||||
en: c55d44c6414e0c4c
|
||||
zh: 9fa2ab31843e259b
|
||||
/agent-note-subagent-tools-own-communication-guidance/consequences:
|
||||
en: 52f7234664c515ec
|
||||
zh: 55ec7b54c044e9af
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Subagent tools own communication guidance
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-22-subagent-tool-owned-communication.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The continuation manager inspected a Symbol on the standard messaging tool before adding tool instructions and parent addressing to the initial task. This coupled orchestration to one tool implementation and made result-delivery guidance depend on tool installation. A published internal entry point carried both that marker and a host prompt adapter used only by tests.
|
||||
|
||||
## Decision
|
||||
|
||||
`tool-subagent-control` owns messaging instructions and parent addressing. Its logged runtime context gives delegated children their direct creator’s name from `ctx.subagents.namesOf()`, explains when to send findings, and states that sending does not finish a turn. The context is emitted only for delegated children when the plugin’s own tool definition is admitted in the current scope and step; roots and ordinary Session forks receive none. Communication guidance follows inherited history without changing the shared system prompt or tool schema.
|
||||
|
||||
The manager owns result delivery. Local activations with `delivery: 'parent'` receive automatic-result guidance even without messaging tools; caller-owned activations receive no parent-delivery promise. Settlement timing, recipients, output, and suppression of closing text after a final message to the parent remain unchanged. Tool visibility grants no messaging authority.
|
||||
|
||||
This supersedes the Symbol identity and initial-task communication-guidance choices in [adjacent messaging](2026-08-27-adjacent-agent-steer-messaging.md). Its message attribution, fixed Steer delivery, and rejection of a separate report operation remain active. The internal entry point and prompt adapter are removed; the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.md) owns caller migration.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep a cross-package marker or add a service hook.** Tool registration already gives its owner the exact definition, and prompt assembly already supplies the current Agent. Another protocol or stored identity has no independent consumer.
|
||||
|
||||
**Move all return guidance into the tool.** Automatic parent notification depends on activation delivery, not on messaging capability. The tool must not promise it to caller-owned activations or withhold it from children without tools.
|
||||
|
||||
## Consequences
|
||||
|
||||
Tests cover visible, hidden and replaced definitions, logged identity, plugin disposal, fork prefix reuse, and parent versus caller result delivery. Recorded-session snapshots cover the assembled prompt and durable runtime context. Tests needing host input use the existing private manager test helper; no production adapter exists solely to expose that helper.
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Subagent 工具拥有通信指导
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-22-subagent-tool-owned-communication.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
继续执行管理器检查标准通信工具上的 Symbol,随后在初始任务中加入工具说明和父级地址。这让编排依赖一个工具实现,也让结果交付指导取决于是否安装工具。已发布的内部入口同时承载该标记和仅供测试使用的宿主 prompt 适配器。
|
||||
|
||||
## 决策
|
||||
|
||||
`tool-subagent-control` 拥有通信说明和父级寻址。持久记录的运行时上下文通过 `ctx.subagents.namesOf()` 提供委派子级的直接创建者名称,说明何时发送发现,并指出发送不会结束轮次。仅当本插件的工具定义在当前作用域和 step 中可用时,才向委派子级提供上下文;根代理和普通 Session fork 不接收。通信指导位于继承历史之后,不改变共享系统提示词或工具 schema。
|
||||
|
||||
管理器拥有结果交付。采用 `delivery: 'parent'` 的本地 activation 即使没有通信工具,也会收到自动结果交付指导;调用方拥有的 activation 不会收到父级交付承诺。结算时序、接收方、输出,以及最后向父级发送消息后省略结束文本的规则保持不变。工具可见性不授予通信权限。
|
||||
|
||||
本决策取代[相邻代理通信](2026-08-27-adjacent-agent-steer-messaging.zh.md)中的 Symbol 身份和初始任务通信指导选择。该记录的消息来源归属、固定 Steer 投递,以及不另设 report 操作的决策继续有效。内部入口和 prompt 适配器已删除;调用方迁移由[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.zh.md)说明。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留跨包标记或增加 service hook。** 工具注册已经向所有者提供确切定义,提示词组装也已提供当前 Agent。另一套协议或身份存储没有独立消费者。
|
||||
|
||||
**把全部返回指导移入工具。** 自动父级通知取决于 activation 的交付方式,而非通信能力。工具不能向调用方拥有的 activation 承诺自动通知,也不能让没有工具的子级缺少交付说明。
|
||||
|
||||
## 后果
|
||||
|
||||
测试覆盖可见、隐藏及替换的定义、持久记录的身份、插件释放、fork 前缀复用,以及父级与调用方结果交付。录制会话快照覆盖组装后的提示词和持久运行时上下文。需要宿主输入的测试使用现有的私有 manager 测试帮助函数;生产代码不再仅为暴露该帮助函数而保留适配器。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-29-cua-dev-signing-identity.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-29-cua-dev-signing-identity.md
|
||||
/agent-note-development-signing-identity-for-source-built-cua-helpers:
|
||||
en: 472c4efd47184a32
|
||||
zh: 49f64628531970e9
|
||||
/agent-note-development-signing-identity-for-source-built-cua-helpers/problem:
|
||||
en: a24857a312482724
|
||||
zh: dbac75d363670e86
|
||||
/agent-note-development-signing-identity-for-source-built-cua-helpers/decision:
|
||||
en: d2265f9ec5fb7a79
|
||||
zh: 05da2479b024a4bb
|
||||
/agent-note-development-signing-identity-for-source-built-cua-helpers/alternatives-considered:
|
||||
en: e30db5bc9c7aab43
|
||||
zh: fa84883b5527334e
|
||||
/agent-note-development-signing-identity-for-source-built-cua-helpers/consequences:
|
||||
en: fb0e8fe44890bdf9
|
||||
zh: d9e9ace1a4c10060
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Development signing identity for source-built CUA helpers
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-29-cua-dev-signing-identity.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Source checkouts compile `DSH Computer Use.app` with an ad-hoc signature. macOS stores Accessibility and Screen Recording grants for a bundle identifier with the designated requirement of the granted code, and an ad-hoc requirement is the code hash. Every changed build and every other worktree therefore needs new grants, and these builds shared the release helper's bundle identifier and replaced its grant entry.
|
||||
|
||||
## Decision
|
||||
|
||||
The Host builds source checkouts as a development variant with bundle identifier `com.deepseek.harness.computer-use.dev` and name `DSH Computer Use Dev`, in `DSH Computer Use Dev.app`. The macOS privacy list and the helper’s reported launch application use the bundle filename, so that filename also includes Dev. The release variant and its packaging are unchanged. Grants stay with the helper application launched through LaunchServices, as the [shared helper decision](2026-10-09-cua-shared-native-helper.md) requires.
|
||||
|
||||
`sourceSigning` belongs to the macOS provider and accepts only default `ad-hoc` or `{ identity, keychain }` selecting an existing certificate and absolute keychain path. Builds do not create, unlock or remove identities. Developer credentials stay separate from the user-facing DSH home, which stores only the certificate selection; the shared `cua` package owns no macOS signing configuration.
|
||||
|
||||
The [developer signing skill](../../../skills/dsh-cua-macos-signing/SKILL.md) guides Agents through creating, reusing and unlocking an isolated identity under `~/.cua-macos-keys/`, then selecting its fingerprint and keychain path in user configuration. The tool stores a random password in a `0600` file, permits codesign access, and adds the dedicated keychain to the user search list. A file lock serializes setup and removal for that directory; a missing keychain with an existing record is preserved and rejected. A directory marker limits identity management to explicitly claimed, initially empty directories. Unexpected files or symlinked identity entries stop setup and removal before changes, so a developer-selected path cannot erase unrelated contents.
|
||||
|
||||
Certificate signatures embed a signing time even without a timestamp server. Development publication records which generation each unsigned bundle and identity produced, then selects that generation again instead of re-signing, so identical sources keep one executable digest and one shared service. The cache uses certificate fingerprints; explicit certificate names are not reused because renewal can change the certificate behind a name. Signing failures stop publication. Developer setup diagnostics omit passwords and complete command arguments. The build CLI defaults to `--identity -` and accepts an existing certificate with `--identity` and optional `--keychain` for release packaging.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Grant the launching terminal.** Launching the service directly makes macOS attribute permissions to the terminal or editor that started DSH. Every program that application runs, including commands run by the model, then has the permissions.
|
||||
|
||||
**Automatically create a fixed identity during source builds.** This makes ordinary startup create developer private keys and ties their lifetime to user data. An explicit developer workflow preserves the option to reduce repeated grants while keeping identity creation, removal and selection under developer control.
|
||||
|
||||
**Self-signed identity in the login keychain.** The login keychain is already unlocked and on the search list, but allowing `codesign` to use a new key there without a dialog requires the user's login password.
|
||||
|
||||
**Add the keychain to the search list only while signing.** The release packaging script does this for one invocation. Concurrent builds in several worktrees would read and restore the user list over each other, and a stopped build would leave the entry behind anyway.
|
||||
|
||||
**A stable launcher that spawns each worktree's helper.** Grants would follow the launcher, but any local process could ask it to run an arbitrary executable with those grants, which is the same exposure as the stored key without a signature check.
|
||||
|
||||
## Consequences
|
||||
|
||||
Development builds signed by the same certificate share a signing requirement; actual permission retention after grants, rebuilds and worktree changes still requires verification. Other processes of the same user can read the stored password and sign with that identity; the release helper has a different bundle identifier. Deleting DSH home removes its selection, not the developer identity; deleting the identity requires clearing every configuration that references it. The real-signing e2e checks reuse, unlocking, requirements across output directories and search-list restoration after removal.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: 源码构建的 CUA helper 使用开发签名身份
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-29-cua-dev-signing-identity.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
源码检出用临时签名编译 `DSH Computer Use.app`。macOS 按 Bundle ID 和被授权代码的指定要求保存辅助功能与屏幕录制授权,临时签名的指定要求就是代码哈希。因此每次构建内容变化、每换一个 worktree,都需要重新授权;这些构建还与正式 helper 共用 Bundle ID,会替换正式版的授权条目。
|
||||
|
||||
## 决策
|
||||
|
||||
Host 以开发版构建源码检出,Bundle ID 为 `com.deepseek.harness.computer-use.dev`,名称为 `DSH Computer Use Dev`,应用包文件名为 `DSH Computer Use Dev.app`。macOS 隐私列表和 helper 返回的启动应用名称使用应用包文件名,因此文件名也需包含 Dev。正式版及其打包流程不变。按[共享 helper 决策](2026-10-09-cua-shared-native-helper.zh.md)的要求,授权仍归经 LaunchServices 启动的 helper 应用。
|
||||
|
||||
`sourceSigning` 由 macOS provider 定义,只接受默认的 `ad-hoc` 和指定已有证书及钥匙串绝对路径的 `{ identity, keychain }`。构建不创建、解锁或移除身份。开发者凭据与面向用户的 DSH home 分开,后者只保存选择证书的配置;共享 `cua` 包不持有 macOS 签名配置。
|
||||
|
||||
[开发者签名 skill](../../../skills/dsh-cua-macos-signing/SKILL.md)指导 Agent 在 `~/.cua-macos-keys/` 创建、复用和解锁独立身份,将证书指纹和钥匙串路径写入用户配置。工具把随机密码存入 `0600` 文件,允许 codesign 访问私钥,并把专用钥匙串加入用户搜索列表。文件锁让同一目录的初始化和移除串行执行;已有记录但钥匙串缺失时保留记录并报错。目录标记将身份管理限制在明确占用的初始空目录中。发现无关文件或身份文件软链接时,初始化和移除都会在修改前停止,避免开发者指定的路径导致无关内容被删除。
|
||||
|
||||
即使不使用时间戳服务器,证书签名也包含签名时间。开发构建记录每份未签名内容与身份对应的已签名目录,再次构建相同内容时复用该目录,保持可执行文件摘要和共享服务一致。缓存使用证书指纹;按证书名称选择时每次签名,避免续期后复用旧证书。签名失败会中止发布。开发者工具的诊断不包含密码或完整命令参数;构建 CLI 默认 `--identity -`,支持指定证书,正式打包也可省略 `--keychain`。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**把权限交给启动终端。** 直接启动服务时,macOS 把权限归属到启动 DSH 的终端或编辑器。该应用运行的所有程序,包括模型执行的命令,都会带上这些权限。
|
||||
|
||||
**源码构建自动创建固定身份。** 这会让普通启动生成开发者私钥,并把身份生命周期绑定到用户数据目录。显式开发者操作保留了减少重新授权的选择,也让证书的创建、移除和使用范围由开发者决定。
|
||||
|
||||
**把自签名身份放进登录钥匙串。** 登录钥匙串已解锁且在搜索列表中,但要让 `codesign` 不弹窗地使用其中的新私钥,需要用户的登录密码。
|
||||
|
||||
**只在签名期间把钥匙串加入搜索列表。** 正式打包脚本对单次调用这样处理。多个 worktree 并发构建时会互相覆盖读取和恢复的列表,构建被中断时条目也会残留。
|
||||
|
||||
**用固定的启动器运行各 worktree 的 helper。** 授权会跟随启动器,但任何本机进程都能让它带着这些授权运行任意可执行文件,暴露面与保存的私钥相同,还少了签名检查。
|
||||
|
||||
## 后果
|
||||
|
||||
同一证书签出的开发构建共用签名要求;授权后重建和跨 worktree 的实际权限保持仍需验证。同一用户的进程可读取保存的密码并使用该身份签名,正式 helper 使用不同的 Bundle ID。删除 DSH home 只移除配置,不删除开发者身份;删除该身份后应清理所有引用它的配置。真实签名 e2e 验证复用、解锁、不同输出目录的签名要求和移除后的搜索列表恢复。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-29-retained-ptc-interruption.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-29-retained-ptc-interruption.md
|
||||
/agent-note-retained-ptc-interruption:
|
||||
en: 482adc6f938bc494
|
||||
zh: 2bcc4856df4fb39e
|
||||
/agent-note-retained-ptc-interruption/problem:
|
||||
en: 1d2ac1280f14aa80
|
||||
zh: c5875c914cf23d73
|
||||
/agent-note-retained-ptc-interruption/decision:
|
||||
en: 77f44a7ce832934c
|
||||
zh: 4744934c60ca6f3d
|
||||
/agent-note-retained-ptc-interruption/alternatives-considered:
|
||||
en: 8c57872ffaf660b2
|
||||
zh: 4765cbf561ea5dc9
|
||||
/agent-note-retained-ptc-interruption/consequences:
|
||||
en: f679322d48a740e9
|
||||
zh: 4a255df6e019a32c
|
||||
@@ -0,0 +1,23 @@
|
||||
# Agent Note: Retained PTC interruption
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-29-retained-ptc-interruption.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Terminating a retained Node process on every timeout or cancellation loses unrelated application objects and imported modules. A control handler on the evaluation thread cannot stop a synchronous loop. The [in-memory execution decision](2026-09-23-best-effort-persistent-ptc.md) still owns disposable state and Session behavior; this decision replaces its timeout and cancellation cleanup policy.
|
||||
|
||||
## Decision
|
||||
|
||||
The host revokes execution authority immediately and requests cooperative stop over the existing control channel. The runtime rejects pending bindings; the original `run()` response confirms that evaluation has settled. Revocation is a one-way notification. The ordered pipe and single active evaluation determine notification ownership; RPC requests associate replies with callers. Acknowledgment permits reuse of the VM and retained objects; earlier mutations remain. Callers await acknowledgment or fallback process cleanup before starting another evaluation.
|
||||
|
||||
The host bounds acknowledgment with `stopGraceMs`. Missing or failed acknowledgment terminates the managed process range and awaits cleanup. Results retain the original timeout or cancellation kind and report whether variables survived. Confirmed termination allows a fresh environment on the next call; failed cleanup blocks reuse until host release succeeds. Explicit reset, disposal and policy invalidation terminate immediately. Reset uses the existing release path and exposes no additional model tool.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
Unconditional termination unnecessarily discards state during ordinary asynchronous waits. Cooperative stop alone cannot bound synchronous loops. Inspector termination is unsuitable as a state-preserving default: local probes on Node 24.9 and 26.6 crashed native async-context handling when terminating an awaited VM loop with an enabled async hook; Node 24 REPL also crashed without an explicit user hook. A successful Inspector command does not establish that the runtime remains reusable. Any future forced in-process interruption must demonstrate safe recovery with the supported Node versions and async hooks.
|
||||
|
||||
## Consequences
|
||||
|
||||
Canceled tools throw normally, allowing user `catch` and `finally` handlers to run. A blocked handler or unresolved user Promise prevents acknowledgment and triggers fallback termination. Timers, arbitrary user Promises, I/O callbacks and user-created Workers or subprocesses have no per-evaluation cancellation guarantee and can still mutate retained state after acknowledgment. Acknowledgment confirms revocation of managed execution, not cancellation of all Node activity. Fallback termination loses all state in that process; neither path rolls back external effects. Host reset remains available when retained resources cannot be trusted.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Agent Note: 保留状态的 PTC 中断
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-29-retained-ptc-interruption.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
每次超时或取消都终止保留的 Node 进程,会丢失无关的应用对象与导入模块。执行线程上的控制处理器无法停止同步死循环。[内存执行决策](2026-09-23-best-effort-persistent-ptc.zh.md)仍负责可丢弃状态与 Session 行为;本决策替代其中的超时与取消清理策略。
|
||||
|
||||
## 决策
|
||||
|
||||
宿主立即撤销本次执行的权限,并通过现有控制通道请求协作式停止。运行时拒绝未完成的绑定,由原 `run()` 响应确认该次执行已结束。撤销是单向通知。有序管道和唯一的活动执行确定通知归属;RPC 请求将回复关联到调用方。收到确认后可复用 VM 和保留对象;此前发生的修改继续存在。调用方等待确认或兜底进程清理完成后,才能开始下一次执行。
|
||||
|
||||
宿主通过 `stopGraceMs` 限制确认等待时间。未收到确认或确认失败时,终止受管进程范围并等待清理。结果保留原来的超时或取消类型,并报告变量是否保留。确认终止后,下一次调用可以创建新环境;清理失败则阻止复用,直到宿主释放成功。显式重置、销毁和策略失效立即终止进程。重置复用现有释放路径,不向模型增加工具。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
无条件终止会在普通异步等待期间丢弃本可保留的状态。仅靠协作式停止无法限制同步死循环。Inspector 终止不适合作为保留状态的默认机制:本地探针在 Node 24.9 和 26.6 启用 async hook 后,终止 await 后的 VM 死循环会导致原生异步上下文处理崩溃;Node 24 REPL 在未显式启用用户 hook 时也会崩溃。Inspector 命令成功不能说明运行时仍可复用。未来若引入进程内强制中断,必须验证支持的 Node 版本及 async hooks 下能安全恢复。
|
||||
|
||||
## 影响
|
||||
|
||||
取消的工具正常抛错,允许用户的 `catch` 和 `finally` 运行。处理器阻塞或用户 Promise 无法完成时,不能确认停止,将触发兜底终止。定时器、任意用户 Promise、I/O 回调及用户创建的 Worker 或子进程不承诺按执行取消,收到确认后仍可能修改保留状态。确认表示受管执行已撤销,不表示所有 Node 活动均已取消。兜底终止会丢失该进程中的全部状态;两条路径均不回滚外部效果。保留资源不可信时,仍可使用宿主重置。
|
||||
@@ -0,0 +1,37 @@
|
||||
# Bilingual-pair consistency record for 2026-10-29-web-provider-selection.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-29-web-provider-selection.md
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers:
|
||||
en: 2a2fa1def8a81625
|
||||
zh: 317d8b115f34e677
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/problem:
|
||||
en: 3f5dacf86015cebb
|
||||
zh: cf0d5bceb9941554
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision:
|
||||
en: 0682ebef17f6a5e7
|
||||
zh: 6ea399459262eb0c
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/registered-providers-are-observable-availability-is-not:
|
||||
en: 38ce796dea91fa35
|
||||
zh: d312a9cd29fd5cc9
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/independent-selection:
|
||||
en: 9d68ffca5c8be681
|
||||
zh: 36bb661f98de0ebb
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/credential-resolution:
|
||||
en: 32f59c35d71a4832
|
||||
zh: e9288313bb18b78d
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/defaults-and-stored-selections:
|
||||
en: 5fcd3319fbceed1d
|
||||
zh: 20771646b9c36fa6
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/decision/legacy-messages-search:
|
||||
en: 2c7c84f8fa1e8745
|
||||
zh: 5d0244e4f17ba12a
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/alternatives-considered:
|
||||
en: 0c227e270894cd71
|
||||
zh: 114484a3e69f5c0a
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/consequences:
|
||||
en: 50f046bf81a7783d
|
||||
zh: 5c0e27bdcaf9f12e
|
||||
/agent-note-web-provider-selection-independent-menus-over-registered-providers/verification:
|
||||
en: 26ade0695094e9bc
|
||||
zh: 7b265c43b1886da5
|
||||
@@ -0,0 +1,85 @@
|
||||
# Agent Note: Web provider selection - independent menus over registered providers
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-29-web-provider-selection.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The web service selects search and fetch providers through two independent fields, `web.searchProvider` and `web.fetchProvider`. A settings control that writes both at once ties fetch billing and target access policy to the search choice, and a control that offers only the DeepSeek choices it knows cannot select a provider that another plugin registers. A picker over registered providers needs the Host to report them, and the [web capability seam](2026-06-24-web-capability-seam.md) has no availability observation for it to reuse. Messages search starts an auxiliary model request for each query, whereas the [Search API](../feature/2026-10-15-deepseek-search-fetch-api.md) returns structured sources directly. The Search and Fetch APIs also need a DeepSeek credential, and a credential setting of their own makes every user reconcile it with the model provider they chat with.
|
||||
|
||||
## Decision
|
||||
|
||||
Users choose search and fetch providers independently from the Host's registered provider lists. The shipped composition selects the Search API for search and local HTTP fetch for fetch, and the DeepSeek Search and Fetch APIs follow the conversation's DeepSeek credential.
|
||||
|
||||
### Registered providers are observable; availability is not
|
||||
|
||||
`WebSearchProvider` and `WebFetchProvider` carry an optional `title`, the provider's display name, and an optional localized `description` of what the provider suits and what it costs or cannot do. `ctx.web.listProviders()` returns `{ search, fetch }`, each a registration-ordered list of `{ id, title?, description? }` entries, and the `web` Remote namespace exposes it as `web/listProviders`. Every registration and disposal emits the payload-free `web/providers-changed` event after the registry changes; the web service contains listener failures, so an observer cannot veto a registry change. [dsh-api-remotes](../../../../packages/api/remotes/README.md) mounts the namespace and forwards the event, and Clients re-read the list after each notification and reconnect.
|
||||
|
||||
The lists include providers whose `available()` answers false. A picker shows what can be pinned, including a provider whose credential the user has not configured yet, whereas availability depends on per-request facts such as the Session's model route and credential state. Availability therefore stays an execution result: `search()` and `fetch()` resolve the configured provider at call time and throw the structured `WebError`, as the seam note records. The tool layer still never enumerates providers.
|
||||
|
||||
### Independent selection
|
||||
|
||||
The Web search and fetch page writes `web.searchProvider` from its Search menu and `web.fetchProvider` from its Fetch menu. Each menu lists every registered provider for its capability by `title` (or `id` when `title` is absent), so plugin providers appear without page changes, and a selected id that the Host does not register stays visible as such. Official providers register no `title`; the page names them from its own locale dictionary and lists them first in a fixed order: the Search API (`deepseek-search-api`) followed by Messages search (`deepseek-official`) for search, and local HTTP fetch (`http`) followed by the Fetch API (`deepseek-fetch-api`) for fetch. Other providers follow in Host registration order. Selecting the deployment's composed provider clears the stored override instead of storing a duplicate value. The [settings page README](../../../../packages/client/ui-settings-web-search/README.md) owns the page's controls.
|
||||
|
||||
Every registered provider is listed; a provider has no flag that keeps it out of the menus. This note supersedes the [Search and Fetch API decision](../feature/2026-10-15-deepseek-search-fetch-api.md)'s rejection of listing every registered provider in a settings dropdown. Independent menus must offer providers that other plugins register, and the display metadata that listing needs is only the optional `title` and `description`. The description lives on the registration rather than in the page's dictionaries so that official and plugin providers explain themselves through one field, and a new provider needs no page change to describe its trade-offs. The rejected generic editor still stands: the page edits only the DeepSeek providers' settings, and other providers keep their own settings pages.
|
||||
|
||||
The Search and Fetch APIs read one `web-deepseek-api` namespace, and each resolves its own credential and endpoint, as the next section records. Messages search reads `web-search-deepseek` for its independent endpoint and key reference.
|
||||
|
||||
### Credential resolution
|
||||
|
||||
Search and Fetch follow the DeepSeek credential of the conversation, and the settings page shows no credential control for them. The conversation's route is the `provider` of the initiating Session's latest request context, which account sign-out also reads; `ready()` uses the route of the model request being prepared, so readiness and execution resolve the same plan.
|
||||
|
||||
On the `deepseek-account` route, with an active, valid `@deepseek-ai/dsh-llm-deepseek-account` Loader entry at the entry root that the model provider directory lists, the operation sends only the account token that `ctx.deepseekAccount` releases for the dispatch endpoint under that route's origin with `/v1/tools`; an HTTP 401 invalidates that token. Every other operation, including one in an account conversation without a sign-in or a token, sends the key under the `apiKeyEnv` of the active `@deepseek-ai/dsh-llm-deepseek-api-key` entry for `deepseek-official`, resolved with that package's own resolver (including `DEEPSEEK_BASE_URL`), to the origin of that route's base URL with `/v1/tools`; without that entry, `DEEPSEEK_API_KEY` goes to `https://api.deepseek.com/v1/tools`. Sending the API key in a signed-out account conversation keeps web tools available there. A user with only `DEEPSEEK_API_KEY`, including every headless, SDK, and ACP run, therefore searches and fetches by default, and a gateway or internal deployment set through the model provider's base URL serves web access at the same origin. A missing key makes the capability not ready, and an operation fails with `WEB_PROVIDER_CREDENTIAL_MISSING` and guidance naming the Models page, the reference, and the account sign-in. A mounted route entry whose settings its resolver rejects is not treated as absent: an operation that needs that route is not ready and fails with `WEB_PROVIDER_MISCONFIGURED` without reading a credential or sending the request, so a broken gateway setting never redirects requests to the official endpoint.
|
||||
|
||||
A capability whose YAML sets a non-empty `searchBaseURL` or `searchApiKeyEnv` (`fetchBaseURL` or `fetchApiKeyEnv` for Fetch) instead always sends the key under its `*ApiKeyEnv` (`DEEPSEEK_API_KEY` when unset) to its `*BaseURL` (the official base when unset) and never the account token, so an intranet or third-party endpoint receives only the key chosen for it. Each capability has its own fields because a deployment can serve search and fetch from different gateways. These fields are YAML-only, and an empty value counts as unset. Stored values of fields outside the schema, including `baseURL`, `apiKeyEnv`, and `apiKey`, load without schema errors and have no effect.
|
||||
|
||||
The [credential helpers](../../../../packages/llm/llm-deepseek/src/credential.ts) of `@deepseek-ai/dsh-llm-deepseek` read and validate API keys, check key presence, and release account tokens for the DeepSeek model routes, Messages search, and the Search and Fetch APIs, so every DeepSeek request reads the credential store the same way; the account model route, Messages search, and the Search and Fetch APIs also invalidate the token that an HTTP 401 rejects. Shipped profiles need no per-profile credential override, because the API-key path works wherever `DEEPSEEK_API_KEY` or the DeepSeek API-key route has a key.
|
||||
|
||||
### Defaults and stored selections
|
||||
|
||||
The [base composition](../../../../packages/bundle/base/cordis.patch.yml) sets `searchProvider: deepseek-search-api` and `fetchProvider: http`. Search defaults to the Search API because it returns sources without Harness starting an auxiliary model request. Fetch defaults to local HTTP fetch because it has no per-request API charge, applies Harness's own URL validation and resource limits, and reaches intranet and local addresses. The Fetch API remains one menu choice away.
|
||||
|
||||
A stored selection, whether in the settings document or in a deployment's own composition, wins over the shipped default, and an upgrade does not rewrite it. A selection is never replaced at execution: when the selected provider is missing, unavailable, or fails, the operation fails with its structured error and does not fall back to another search or fetch provider. A fallback would send the query and credential to an endpoint the user did not choose and change billing without notice.
|
||||
|
||||
### Legacy Messages search
|
||||
|
||||
The DeepSeek Messages search provider remains available as **DeepSeek (legacy)**. A stored `web.searchProvider: deepseek-official` (or `DSH_WEB_SEARCH_PROVIDER=deepseek-official`) selects it, so users whose gateways serve Messages search but not `/v1/tools` can retain their working provider.
|
||||
|
||||
The Messages provider owns and writes `web/deepseek-search-llm-request`. The declaration and payload retain compatibility with released Sessions; the Search and Fetch API package neither declares nor writes this auxiliary event.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep one switch for both APIs.** It lets Fetch API adoption need no configuration edit, but it ties fetch billing and target access policy to the search choice, and it cannot express a provider that another plugin registers.
|
||||
|
||||
**Filter the lists by availability, or add availability to each entry.** A filtered list hides a provider while its credential is missing, right when the user needs to choose and configure it. Availability also depends on the request's model route and credential state, so a listed boolean could disagree with execution. The archived [observation-surface decision](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) allows only the smallest signal a consumer reads; the menus read registration only.
|
||||
|
||||
**Keep a fixed provider catalog in the Client.** The Client would show ids the Host does not register as normal choices and would never show a provider that a plugin registers at runtime.
|
||||
|
||||
**Carry the provider list in the change event.** A payload-free event leaves one read path for initial load, notifications, and reconnects, so the Client never has to order an event payload against a concurrent read.
|
||||
|
||||
**Keep display names in the Client or in a separate Host registry.** The registering plugin knows its provider's name; a separate map would let a provider and its label register or dispose independently.
|
||||
|
||||
**Fall back to another provider when the selected one fails.** A fallback hides gateway and credential misconfiguration, sends the query to a second service, and changes what the request costs without the user choosing it.
|
||||
|
||||
**Remove Messages search and let a stored `deepseek-official` select the Search API.** Gateways can support Messages search without Search API routes. Retaining the legacy provider preserves that choice instead of changing its protocol and billing.
|
||||
|
||||
**Default fetch to the Fetch API as well.** It adds a per-request charge to every fetch and delegates page retrieval and target access policy to the service, which fetches from its own side rather than from the user's machine.
|
||||
|
||||
**Choose a credential source per capability on the settings page**, from a menu listing the configured DeepSeek model providers plus a custom key and base URL, without fallback between sources. The visible setting would state the whole rule, but the configuration surface is too large for users to understand: each capability adds a credential menu that the user must keep in step with the model provider they chat with, and a signed-in user chatting on the account route gets no web tools until they change it. Following the conversation's credential needs no setting, and the YAML-only own fields cover endpoints that differ from the model provider.
|
||||
|
||||
**Share one custom key and base URL between Search and Fetch.** It cannot express a deployment that serves the two APIs from different gateways.
|
||||
|
||||
**Fail an account conversation without a sign-in instead of sending the API key.** It would keep the account route from ever billing the API key, but signed-out account conversations would lose web tools even when a DeepSeek API key is configured.
|
||||
|
||||
**Send the account token whenever the user is signed in, whatever the conversation's route.** It would bill web access to the account during conversations on the API-key route or on another provider, which the user did not choose for them.
|
||||
|
||||
## Consequences
|
||||
|
||||
Users without a stored search selection search through the Search API: their queries go to the Search endpoint the conversation's DeepSeek credential selects, cost a Search API request each, and send up to 128 characters of the latest human prompt as `ori_question` unless `sendOriginalQuestion` is off. Deployments whose gateway has no `/v1/tools` routes get search failures until they select Messages search or set `searchBaseURL` to a base that serves them. A stored `deepseek-official` selection uses Messages search. Users without a stored fetch selection fetch through local HTTP fetch. A signed-in user without an API key has DeepSeek web search only in conversations on the account route; the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.md) lists each affected setup.
|
||||
|
||||
A plugin provider, including one written in creator mode, appears in the matching menu as soon as it registers, under its `title`. The menus can offer a provider that cannot run for the current request; the failure appears when a search or fetch runs. Each registry change costs one forwarded event and one list read in each Client that observes it.
|
||||
|
||||
## Verification
|
||||
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) covers list contents and order, titles, events after registration and disposal, no event for a rejected duplicate, and contained listener failures. [deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) covers the independent Messages provider, and [credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) covers, for both capabilities, the account token on the account route and its invalidation after HTTP 401, the API-key fallback without a sign-in, the route key and endpoint in official, non-DeepSeek, and Session-less conversations, `DEEPSEEK_API_KEY` and the official base without that route, YAML-only own fields that never send the token, independent capabilities, readiness matching each operation, and ignored removed fields; [composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) boots the real model routes through the Loader. [remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) covers event forwarding, and [base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) pins the shipped defaults. The settings page's controller and card specs and the [plugin configuration browser scenario](../../../../apps/web/tests/plugin-config.e2e.ts) cover menu contents, independent Search and Fetch selection, the absence of credential controls for the DeepSeek APIs, and the settings each provider keeps.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Agent Note: Web 提供方选择——基于已注册提供方的独立下拉框
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-29-web-provider-selection.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Web 服务通过 `web.searchProvider` 和 `web.fetchProvider` 两个独立字段分别选择搜索与抓取提供方。用一个设置控件同时写入两者,会把抓取的计费和目标访问策略绑在搜索选择上;只提供已知 DeepSeek 选项的控件,也无法选用其他插件注册的提供方。基于已注册提供方的选择器需要 Host 报告这些提供方,而 [Web 能力 seam](2026-06-24-web-capability-seam.zh.md) 没有可供复用的可用性观察接口。Messages 搜索每次查询都会发起一次辅助模型请求,而 [Search API](../feature/2026-10-15-deepseek-search-fetch-api.zh.md) 能直接返回结构化来源。Search 与 Fetch API 还需要 DeepSeek 凭据;为它们单独设置凭据,会让每个用户都要把它与自己对话所用的模型提供方对齐。
|
||||
|
||||
## 决策
|
||||
|
||||
用户从 Host 已注册的提供方列表中分别选择搜索和抓取提供方。内置组合将搜索设为 Search API,将抓取设为本地 HTTP 抓取;DeepSeek Search 与 Fetch API 跟随会话的 DeepSeek 凭据。
|
||||
|
||||
### 已注册的提供方可观察,可用性不可观察
|
||||
|
||||
`WebSearchProvider` 和 `WebFetchProvider` 带有可选的 `title`(提供方的显示名称)和可选的多语言 `description`(说明该提供方适合什么、有什么代价或做不到什么)。`ctx.web.listProviders()` 返回 `{ search, fetch }`,二者均为按注册顺序排列的 `{ id, title?, description? }` 列表,`web` Remote namespace 将其暴露为 `web/listProviders`。每次注册和注销都会在注册表变更后触发无载荷的 `web/providers-changed` 事件;Web 服务会隔离监听器异常,因此观察者无法阻断注册表变更。[dsh-api-remotes](../../../../packages/api/remotes/README.zh.md) 挂载该 namespace 并转发该事件,Client 在收到通知和重连后重新读取列表。
|
||||
|
||||
列表包含 `available()` 返回 false 的提供方。选择器展示的是可固定选用的项,包括用户尚未配置凭据的提供方;而可用性取决于会话的模型路由、凭据状态等单次请求条件。因此可用性仍是执行期结果:`search()` 和 `fetch()` 在调用时解析所配置的提供方并抛出结构化 `WebError`,与 seam 记录一致。工具层仍从不枚举提供方。
|
||||
|
||||
### 分别选择
|
||||
|
||||
「网页搜索与获取」页由 Search 下拉框写入 `web.searchProvider`,由 Fetch 下拉框写入 `web.fetchProvider`。每个下拉框以 `title`(缺省时用 `id`)列出该能力的所有已注册提供方,因此插件提供方无需修改页面即可出现,且 Host 未注册的已选 id 仍会照常显示。官方提供方不注册 `title`,由页面用自己的语言字典命名,并按固定顺序排在最前:搜索依次为 Search API(`deepseek-search-api`)与 Messages 搜索(`deepseek-official`),抓取依次为本地 HTTP 抓取(`http`)和 Fetch API(`deepseek-fetch-api`)。其他提供方按 Host 注册顺序排列。选择部署组合的默认提供方会清除已保存的覆盖值,而非冗余保存一份副本。[设置页 README](../../../../packages/client/ui-settings-web-search/README.zh.md) 负责页面控件说明。
|
||||
|
||||
所有已注册的提供方都会列出;提供方没有可以把自己排除在下拉框之外的标志。本 Note 取代了 [Search 与 Fetch API 决策](../feature/2026-10-15-deepseek-search-fetch-api.zh.md)对「在设置页用下拉框列出所有已注册的提供方」的否决。独立的下拉框必须提供其他插件注册的提供方,而列出提供方所需的展示元数据只是可选的 `title` 和 `description`。说明放在注册信息里而不是页面词典里,这样官方与插件提供方都通过同一个字段说明自己,新提供方无需修改页面即可说明其取舍。被否决的通用编辑表单仍然成立:该页面只编辑 DeepSeek 提供方的设置,其他提供方保留各自的设置页。
|
||||
|
||||
Search 与 Fetch API 读取同一个 `web-deepseek-api` namespace,并各自解析自己的凭据和端点,见下一节。Messages 搜索从 `web-search-deepseek` 读取独立的端点和密钥引用。
|
||||
|
||||
### 凭据解析
|
||||
|
||||
Search 与 Fetch 跟随会话的 DeepSeek 凭据,设置页不为它们显示凭据控件。会话的路由是发起会话最近一次请求上下文的 `provider`,账号退出登录读取的也是它;`ready()` 使用正在准备的模型请求的路由,因此就绪判断与执行解析出相同的方案。
|
||||
|
||||
在 `deepseek-account` 路由上,若模型提供方目录列出了入口根部活动且有效的 `@deepseek-ai/dsh-llm-deepseek-account` Loader 条目,操作只发送 `ctx.deepseekAccount` 为该路由 origin 加 `/v1/tools` 下的请求端点给出的账号令牌;HTTP 401 会作废该令牌。其他所有操作,包括账号会话中没有登录或没有令牌的操作,都把 `deepseek-official` 的活动 `@deepseek-ai/dsh-llm-deepseek-api-key` 条目的 `apiKeyEnv` 下的密钥(用该包自己的解析函数解析,包括 `DEEPSEEK_BASE_URL`)发往该路由基础地址的 origin 加 `/v1/tools`;没有该条目时,把 `DEEPSEEK_API_KEY` 发往 `https://api.deepseek.com/v1/tools`。未登录的账号会话发送 API 密钥,网页工具因此在其中仍然可用。因此只有 `DEEPSEEK_API_KEY` 的用户(包括所有 headless、SDK 与 ACP 运行)默认即可搜索和抓取,通过模型提供方基础地址设置的网关或内部部署也在同一 origin 上提供 Web 访问。缺少密钥时,该能力未就绪,操作以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败,指引信息点名模型页面、该引用和账号登录。已挂载、但其解析函数拒绝其设置的路由条目不视为不存在:需要该路由的操作未就绪,并以 `WEB_PROVIDER_MISCONFIGURED` 失败,不读取凭据、不发送请求,因此网关设置出错时请求不会被改发到官方端点。
|
||||
|
||||
在 YAML 中设置了非空 `searchBaseURL` 或 `searchApiKeyEnv`(Fetch 为 `fetchBaseURL` 或 `fetchApiKeyEnv`)的能力,则始终把其 `*ApiKeyEnv`(未设置时为 `DEEPSEEK_API_KEY`)下的密钥发往其 `*BaseURL`(未设置时为官方基础地址),从不发送账号令牌,因此内网或第三方端点只收到为它选择的密钥。每个能力有自己的字段,因为一个部署可以由不同网关提供搜索和抓取。这些字段只能在 YAML 中设置,空值视为未设置。schema 之外字段的已保存值,包括 `baseURL`、`apiKeyEnv` 和 `apiKey`,可以加载而不产生 schema 错误,且不生效。
|
||||
|
||||
`@deepseek-ai/dsh-llm-deepseek` 的[凭据函数](../../../../packages/llm/llm-deepseek/src/credential.ts)为 DeepSeek 模型路由、Messages 搜索以及 Search 与 Fetch API 读取并校验 API 密钥、检查密钥是否存在、取得账号令牌,因此每个 DeepSeek 请求都以同一方式读取凭据库;账号模型路由、Messages 搜索以及 Search 与 Fetch API 还会作废被 HTTP 401 拒绝的令牌。内置配置不需要按 profile 覆盖凭据,因为只要 `DEEPSEEK_API_KEY` 或 DeepSeek API 密钥路由有密钥,API 密钥路径就能工作。
|
||||
|
||||
### 默认值与已保存的选择
|
||||
|
||||
[base 组合](../../../../packages/bundle/base/cordis.patch.yml)设置 `searchProvider: deepseek-search-api` 和 `fetchProvider: http`。搜索默认使用 Search API,因为它返回来源时无需 Harness 发起辅助模型请求。抓取默认使用本地 HTTP 抓取,因为它没有按次的 API 费用,执行 Harness 自身的 URL 校验和资源限制,并且可以访问内网和本地地址。Fetch API 仍可在下拉框中直接选择。
|
||||
|
||||
已保存的选择——无论位于设置文档还是部署自身的组合中——优先于内置默认值,升级不会改写它。执行时从不替换选择:所选提供方缺失、不可用或失败时,操作以结构化错误失败,不会回退到其他搜索或抓取提供方。回退会把查询和凭据发往用户未选择的端点,并在不提示的情况下改变计费。
|
||||
|
||||
### 旧版 Messages 搜索
|
||||
|
||||
DeepSeek Messages 搜索提供方仍以 **DeepSeek(旧版)**提供。已保存的 `web.searchProvider: deepseek-official`(或 `DSH_WEB_SEARCH_PROVIDER=deepseek-official`)会选中它,因此网关支持 Messages 搜索但不提供 `/v1/tools` 的用户可以保留可用的提供方。
|
||||
|
||||
Messages 提供方负责声明并写入 `web/deepseek-search-llm-request`。声明和载荷保持与已发布会话兼容;Search 与 Fetch API 包既不声明也不写入这一辅助事件。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留一个同时切换两个 API 的开关。** 它让使用 Fetch API 无需修改配置,但把抓取的计费和目标访问策略绑在搜索选择上,也无法表达其他插件注册的提供方。
|
||||
|
||||
**按可用性过滤列表,或在每个条目中加入可用性。** 过滤后的列表恰好会在凭据缺失时隐藏提供方,而这正是用户需要选择并配置它的时候。可用性还取决于请求的模型路由和凭据状态,列表中的布尔值可能与实际执行结果不一致。已归档的[观察面决策](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)只允许引入消费方实际读取的最小信号;下拉框只读取注册情况。
|
||||
|
||||
**在 Client 中维护固定的提供方目录。** Client 会把 Host 未注册的 id 当作正常选项展示,且永远无法展示插件在运行时注册的提供方。
|
||||
|
||||
**在变更事件中携带提供方列表。** 无载荷事件让初次加载、变更通知和断线重连共用同一条读取路径,Client 无需为事件载荷与并发读取结果排序。
|
||||
|
||||
**把显示名称放在 Client 或单独的 Host 注册表中。** 注册插件最清楚自身提供方的名称;拆成独立映射会让提供方与其标签各自独立注册或注销。
|
||||
|
||||
**所选提供方失败时回退到其他提供方。** 回退会掩盖网关和凭据的配置错误,将查询发往第二个服务,并在未经用户选择的情况下改变请求的费用。
|
||||
|
||||
**移除 Messages 搜索,让已保存的 `deepseek-official` 选中 Search API。** 网关可能支持 Messages 搜索,却没有 Search API 路由。保留旧版提供方可以保留这一选择,而不改变其协议和计费。
|
||||
|
||||
**抓取也默认使用 Fetch API。** 它会给每次抓取增加按次费用,并把页面抓取和目标访问策略交给服务端,由服务端而不是用户的机器发起抓取。
|
||||
|
||||
**在设置页为每个能力选择一种凭据来源**,下拉框列出已配置的 DeepSeek 模型提供方,外加自定义密钥和基础地址,来源之间不回退。这样可见的设置就能说明全部规则,但配置面过大,用户难以理解:每个能力都多出一个凭据下拉框,用户必须让它与自己对话所用的模型提供方保持一致;在账号路由上对话的已登录用户在修改设置前没有网页工具。跟随会话的凭据不需要任何设置,与模型提供方不同的端点由只能在 YAML 中设置的单独字段覆盖。
|
||||
|
||||
**Search 与 Fetch 共用一个自定义密钥和基础地址。** 它无法表达由不同网关分别提供两个 API 的部署。
|
||||
|
||||
**账号会话未登录时直接失败,而不是发送 API 密钥。** 这样账号路由永远不会消耗 API 密钥,但即使配置了 DeepSeek API 密钥,未登录的账号会话也会失去网页工具。
|
||||
|
||||
**只要用户已登录就发送账号令牌,不论会话的路由。** 这会让在 API 密钥路由或其他提供方上进行的会话把 Web 访问记到账号上,而用户并没有为这些会话这样选择。
|
||||
|
||||
## 影响
|
||||
|
||||
没有已保存搜索选择的用户通过 Search API 搜索:查询发往会话的 DeepSeek 凭据所选的 Search 端点,每次计为一次 Search API 请求,并且除非关闭 `sendOriginalQuestion`,还会把最近一条用户提示的至多 128 个字符作为 `ori_question` 发送。网关没有 `/v1/tools` 路由的部署会遇到搜索失败,直到选择 Messages 搜索,或把 `searchBaseURL` 设为提供这些路由的基础地址。已保存的 `deepseek-official` 选择使用 Messages 搜索。没有已保存抓取选择的用户通过本地 HTTP 抓取。已登录但没有 API 密钥的用户只在账号路由上的会话中可以使用 DeepSeek 网页搜索;[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.zh.md)列出了每种受影响的配置。
|
||||
|
||||
插件提供方(包括在创作模式下编写的提供方)一经注册,就会以其 `title` 出现在对应的下拉框中。下拉框可能提供在当前请求中无法运行的提供方;失败会在执行搜索或抓取时出现。每次注册表变更会在每个观察它的 Client 中产生一次转发事件和一次列表读取。
|
||||
|
||||
## 验证
|
||||
|
||||
[web.spec.ts](../../../../packages/web/web/tests/web.spec.ts) 覆盖列表内容与顺序、标题、注册与撤销后的事件、重复注册被拒绝时不触发事件,以及监听器失败的隔离。[deepseek.spec.ts](../../../../packages/web/web-search-deepseek/tests/deepseek.spec.ts) 覆盖独立的 Messages 提供方,[credential-resolution.spec.ts](../../../../packages/web/web-deepseek-api/tests/credential-resolution.spec.ts) 针对两个能力覆盖账号路由上的账号令牌及其在 HTTP 401 后被作废、未登录时回退到 API 密钥、官方、非 DeepSeek 和会话之外的操作使用路由密钥与端点、没有该路由时使用 `DEEPSEEK_API_KEY` 与官方基础地址、从不发送令牌的 YAML 单独字段、两个能力相互独立、就绪判断与每次操作一致,以及被忽略的已移除字段;[composition.spec.ts](../../../../packages/web/web-deepseek-api/tests/composition.spec.ts) 通过 Loader 启动真实的模型路由。[remote-events.host.spec.ts](../../../../packages/api/remotes/tests/remote-events.host.spec.ts) 覆盖事件转发,[base.spec.ts](../../../../packages/bundle/base/tests/base.spec.ts) 固定内置默认值。设置页的 controller 与 card 测试以及[插件配置浏览器场景](../../../../apps/web/tests/plugin-config.e2e.ts)覆盖下拉框内容、Search 与 Fetch 的分别选择、DeepSeek API 不显示凭据控件,以及各提供方保留的设置。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-11-02-subagent-catalog-names.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-11-02-subagent-catalog-names.md
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog:
|
||||
en: 987d2272bb47fe75
|
||||
zh: 54e701ecbaa60457
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/problem:
|
||||
en: d4798e7bbbd5d49d
|
||||
zh: b388470cf1ff33ac
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/decision:
|
||||
en: 8fafb256536d1bc5
|
||||
zh: f45f9e6d6f9a2407
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/alternatives-considered:
|
||||
en: 14b564fd848248da
|
||||
zh: 5f65c05bbe22d581
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/consequences:
|
||||
en: 4baa2557e5415ee1
|
||||
zh: 8e8d7819c04f9043
|
||||
/agent-note-subagent-names-live-in-the-creator-s-catalog/testing:
|
||||
en: a7963eadbfe52c21
|
||||
zh: 10bfc9fb28f6ae73
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Subagent names live in the creator's catalog
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-02-subagent-catalog-names.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Models coordinate subagents through `send_message`, `interrupt_agent`, `list_agents`, and Team task owners. Those tools addressed every member by a 36-character Session id. Models copy such ids poorly: they cannot recall which id belongs to which task, they mistype ids, and every listing, receipt, notice, and identity statement spends tokens on them. Descriptions explain responsibilities but repeat and cannot address a member. Models need short, readable references that survive restarts and resolve to exactly one member, without a second execution identity.
|
||||
|
||||
## Decision
|
||||
|
||||
The Session id remains the only execution identity for routing, authorization, persistence, task ownership, and navigation. A name is a model-facing reference to it.
|
||||
|
||||
Each new child has a name that is unique among its creator's children. The name is a field of the `subagent/catalog` entry that the creator already appends when it creates the child, using catalog payload version 3. Local and parent-delivered external creation write it; caller-delivered external runs enter no catalog and take no name. A name is 1–32 lower-case letters, digits, or hyphens starting with a letter, and is never `lead`. A 36-character UUID therefore never parses as a name. Omission selects the first free `agent-N`. An explicit name already held by a sibling fails with `DUPLICATE_AGENT_NAME`, and an invalid name fails with `INVALID_AGENT_NAME`.
|
||||
|
||||
The creator reserves the name synchronously at admission, before startup awaits anything. The reservation lives in process memory beside the creator's committed catalog names and is released when the catalog entry commits or startup fails. Task assignment copies the immutable full name into optional `ownerName` beside `ownerId` in the same task event. Task reads use this saved value and fall back to `ownerId`, so display does not require catalog traversal.
|
||||
|
||||
The root is always `lead`. A descendant's full name is the slash-separated path of names from the root without a `lead/` prefix, such as `reviewer/checker`. Sibling-unique segments make full names unique in the tree. A child whose catalog entry predates names, and every descendant below it, keeps its Session id as its name.
|
||||
|
||||
`ctx.subagents.resolveName()` returns the Session id for `lead` or a full name by reading only the catalogs along the path; any other text is returned unchanged as a Session id. Tools resolve before they call messaging, interruption, or Team assignment, and those operations authorize the resulting Session id. Resolution grants no authority. Runtime-authored text names agents by full name, while durable attribution such as `senderSessionId` and Team `ownerId` keeps Session ids. The [subagent runtime](../../../../packages/subagent/subagent/README.md#agent-names) owns the API contract and the [upgrade guide](../../../../docs/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.md) owns caller migration.
|
||||
|
||||
This supersedes the rejection of "unique names as sibling-routing opt-in" and the statement that creation accepts no member name in [subagent teammates](2026-10-15-agent-teams-as-subagents.md), and the Session-id identity text in [required runtime-context identity](2026-10-13-team-identity-runtime-context.md). Their single-identity, Team, and runtime-context retention decisions remain active.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Optional naming plugin with a root-Session binding event.** A separate plugin would record a name-to-Session binding in the root Session after the child starts. That adds a second write that can fail independently after startup succeeds, so a started child can lack its binding, and binding after start can report errors for children that are already running. The presentation plugin would then own cancellation of a lifecycle it does not control, would rewrite runtime-authored messages that name agents, and would still change model-visible schemas when it is absent, because the core tools must accept and render either form. Recording the name in the creator's existing catalog fact needs no second write, no rollback protocol, and no optional service.
|
||||
|
||||
**Flat names unique across the tree.** A tree-wide flat namespace needs one registry at the root that every creation must consult and update, including creations by descendants whose root may be cold. Sibling-unique segments joined into paths are unique without a root registry, and each creator checks only its own catalog.
|
||||
|
||||
**Recyclable names.** Releasing a name when its child settles would let a later sibling take it. A model that still remembers the earlier child would then message or interrupt a different agent. A name stays taken for as long as the catalog entry exists.
|
||||
|
||||
**Automatic suffixes on conflict.** Turning a duplicate `reviewer` into `reviewer-2` would let creation succeed, but the model would believe it holds a name that the runtime never assigned. A duplicate fails so that the model chooses a name it knows.
|
||||
|
||||
## Consequences
|
||||
|
||||
Catalog payload version 3 is a same-format addition: `SESSION_FORMAT_VERSION` is unchanged, current readers accept versions 0 through 3, and releases that predate names reject version 3 loudly. The `subagentCatalog` projection state version remains 6 because old checkpoint facts keep their meaning and accept named suffix events; invalidating them would add full catalog folds to cold descendant observations without changing the recovered state. Native admission rejects a repeated sibling name in own catalog facts, so a log that could make name lookup ambiguous fails before any member is resolved. The [persistence acknowledgement](../../../../docs/persistence-changes/2026-11-02-subagent-catalog-names.md) records the type change.
|
||||
|
||||
Tool schemas grow by one optional `name` parameter on each delegation tool, and `send_message` and `interrupt_agent` rename `agent_id` to `agent`. These schema changes invalidate cached request prefixes once after upgrading. Identity runtime context, receipts, settlement notices, agent messages, and listing rows become shorter for shallow trees, while full names lengthen with nesting depth. `list_agents` drops parent ids and depth because a full name encodes both.
|
||||
|
||||
Names cannot be renamed or recycled. Historical children have no names, and no migration assigns them. Name reservations are process-local, as is subagent residency. Tools still accept Session ids, so recorded Session ids and members below unnamed creators remain addressable.
|
||||
|
||||
## Testing
|
||||
|
||||
Runtime tests cover requested and default names, duplicate and invalid names rejected before startup, reservation across concurrent startup and release after failed startup, nested paths and `namesOf`, resolution of names, unknown text, and Session ids without granting authority, cold restoration of a member's path from the catalogs along its creation chain, and Session-id addressing below an unnamed historical creator. Catalog tests restore version-3 names beside unnamed historical entries, and the V4 format reader admits native version-3 catalog facts. Control-tool and Team-tool tests cover name-addressed messaging, listing rows, owner names, and historical owners shown by Session id. Recorded sessions pin the tool schemas, identity runtime context, receipts, listing rows, and Team policy text.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Agent Note: Subagent 名称记录在创建者目录中
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-02-subagent-catalog-names.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
模型通过 `send_message`、`interrupt_agent`、`list_agents` 和 Team 任务所有者协调 subagent(子智能体)。这些工具曾以 36 字符的 Session id 寻址每个成员。模型难以准确复制这类 id:记不清哪个 id 对应哪个任务,会输错 id,而且每次列表、回执、通知和身份声明都要为其消耗 token。描述能说明职责,但可以重复,无法寻址成员。模型需要简短、可读、跨重启有效且只解析到唯一成员的引用,同时不引入第二种执行身份。
|
||||
|
||||
## Decision
|
||||
|
||||
Session id 仍是路由、授权、持久化、任务归属和导航使用的唯一执行身份。名称是指向它的面向模型的引用。
|
||||
|
||||
每个新子级都有一个名称,在其创建者的子级之间唯一。名称是创建者在创建子级时本就会追加的 `subagent/catalog` 条目中的一个字段,使用第 3 版目录载荷。本地创建和父级投递的外部创建写入名称;调用方投递的外部执行不进入目录,也没有名称。名称由 1–32 个小写字母、数字或连字符组成,以字母开头,且绝不能是 `lead`。因此 36 字符的 UUID 永远不会被解析为名称。省略时选择第一个空闲的 `agent-N`。显式请求同级已持有的名称会以 `DUPLICATE_AGENT_NAME` 失败,无效名称以 `INVALID_AGENT_NAME` 失败。
|
||||
|
||||
创建者在准入时、启动等待任何操作之前同步预留名称。预留保存在进程内存中,与创建者已提交的目录名称并列,并在目录条目提交或启动失败时释放。任务分配在同一个任务事件中,将不可变的完整名称复制到 `ownerId` 旁的可选 `ownerName`。任务读取使用此保存值,缺失时回退到 `ownerId`,因此展示无需遍历目录。
|
||||
|
||||
根的名称始终为 `lead`。后代的完整名称是从根开始、以斜杠分隔的名称路径,不带 `lead/` 前缀,例如 `reviewer/checker`。同级唯一的各段使完整名称在整棵树中唯一。目录条目早于名称机制的子级及其下所有后代继续以 Session id 作为名称。
|
||||
|
||||
`ctx.subagents.resolveName()` 只读取路径上的目录,为 `lead` 或完整名称返回 Session id;其他文本原样作为 Session id 返回。工具先解析名称,再调用消息、中断或 Team 分配,由这些操作对解析出的 Session id 授权。解析本身不授予权限。运行时撰写的文本以完整名称指称 agent,而 `senderSessionId` 和 Team `ownerId` 等持久归属仍保留 Session id。[subagent 运行时](../../../../packages/subagent/subagent/README.zh.md#agent-names)定义 API 约定,[升级指南](../../../../docs/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.zh.md)负责调用方迁移。
|
||||
|
||||
本决策取代 [Agent Teams 队友是子代理](2026-10-15-agent-teams-as-subagents.zh.md)中对“以唯一名称选择性启用同级路由”的否决以及“创建不接收成员名称”的表述,并取代[必需运行时身份](2026-10-13-team-identity-runtime-context.zh.md)中以 Session id 表述身份的文本。它们的单一身份、Team 和运行时上下文保留决策仍然有效。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**带根 Session 绑定事件的可选命名插件。** 独立插件会在子级启动后,在根 Session 中记录名称到 Session 的绑定。这会增加第二次写入,且可能在启动成功后单独失败,导致已启动的子级缺少绑定;启动后再绑定还可能为已在运行的子级报告错误。展示用插件因此要负责取消一个不受它控制的生命周期,要改写指称 agent 的运行时消息;而且即使插件不存在,面向模型的 schema 仍会改变,因为核心工具必须接受并渲染两种形式。把名称记录在创建者已有的目录事实中,不需要第二次写入、回滚协议或可选服务。
|
||||
|
||||
**全树唯一的扁平名称。** 全树扁平命名空间需要根上的一个注册表,每次创建都要查询并更新它,包括根可能处于冷状态时由后代发起的创建。同级唯一的各段连接成路径后无需根注册表即可唯一,每个创建者只检查自己的目录。
|
||||
|
||||
**可回收名称。** 子级结算后释放名称会让之后的同级取得该名称。仍记得先前子级的模型会因此向另一个 agent 发消息或发出中断。只要目录条目存在,名称就保持占用。
|
||||
|
||||
**冲突时自动加后缀。** 把重复的 `reviewer` 改为 `reviewer-2` 能让创建成功,但模型会以为自己持有一个运行时从未分配的名称。重复名称直接失败,模型才会选择一个自己知道的名称。
|
||||
|
||||
## Consequences
|
||||
|
||||
第 3 版目录载荷是同格式增量:`SESSION_FORMAT_VERSION` 不变,当前读取器接受第 0 至第 3 版,早于名称机制的发布会明确拒绝第 3 版。`subagentCatalog` 投影状态版本保持为 6,因为旧检查点事实的含义不变,且能继续处理具名后缀事件;使其失效会在冷后代观察时增加完整目录折叠,却不改变恢复结果。原生准入会拒绝自身目录事实中重复的同级名称,因此会让名称查找产生歧义的日志在解析任何成员之前就失败。[持久化确认记录](../../../../docs/persistence-changes/2026-11-02-subagent-catalog-names.zh.md)记录了该类型变更。
|
||||
|
||||
每个委派工具的 schema 增加一个可选 `name` 参数,`send_message` 和 `interrupt_agent` 把 `agent_id` 改名为 `agent`。这些 schema 变更会在升级后使缓存的请求前缀失效一次。对浅层树而言,身份运行时上下文、回执、结算通知、agent 消息和列表行都变短,完整名称则随嵌套深度变长。`list_agents` 不再携带父级 id 和深度,因为完整名称已包含这两项信息。
|
||||
|
||||
名称不能修改或回收。历史子级没有名称,也没有迁移为其分配名称。名称预留与 subagent 驻留一样仅限进程内。工具仍接受 Session id,因此已记录的 Session id 和无名创建者下的成员仍可寻址。
|
||||
|
||||
## Testing
|
||||
|
||||
运行时测试覆盖请求名称与默认名称、启动前拒绝重复和无效名称、并发启动期间的预留和启动失败后的释放、嵌套路径与 `namesOf`、在不授予权限的前提下解析名称、未知文本和 Session id、从创建链上的目录冷恢复成员路径,以及无名历史创建者之下以 Session id 寻址。目录测试在无名历史条目旁恢复第 3 版名称,V4 格式读取器接纳原生第 3 版目录事实。控制工具和 Team 工具测试覆盖按名称发消息、列表行、所有者名称,以及以 Session id 显示的历史所有者。录制会话固定工具 schema、身份运行时上下文、回执、列表行和 Team 策略文本。
|
||||
@@ -0,0 +1,25 @@
|
||||
# Bilingual-pair consistency record for 2026-11-03-hoc-slot-kind.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-11-03-hoc-slot-kind.md
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers:
|
||||
en: 8292b8f9884b8802
|
||||
zh: 3a4f34ca79313c9d
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/problem:
|
||||
en: d16c3bfef49e16be
|
||||
zh: 6714da1c9c984fe9
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/decision:
|
||||
en: 3a4380324cfc1679
|
||||
zh: 44db368e091cbd46
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/decision/implementation:
|
||||
en: 01189e457c91deeb
|
||||
zh: 94f7e8865b332a07
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/decision/first-consumer:
|
||||
en: 1afdf4ceeef7a3b3
|
||||
zh: be01cf39850db0ca
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/alternatives-considered:
|
||||
en: b4824619c502b422
|
||||
zh: 610364f2dc8cb2b7
|
||||
/agent-note-add-the-hoc-slot-kind-so-plugins-can-wrap-owner-content-in-layers/consequences:
|
||||
en: 7542ee2bc2c70258
|
||||
zh: bc0dd1dc6e50de66
|
||||
@@ -0,0 +1,72 @@
|
||||
# Agent Note: Add the `hoc` slot kind so plugins can wrap owner content in layers
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-03-hoc-slot-kind.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The home whale plays when blank space is clicked. Its canvas must paint below the content, and `.scrollBody` fills the whole body, so the canvas itself never receives clicks; only an ancestor of the content does.
|
||||
|
||||
No existing slot kind lets a plugin become an ancestor of the content:
|
||||
|
||||
| Kind | Behavior | Why it is not enough |
|
||||
|---|---|---|
|
||||
| `single` / `list` / `keyed` | Render the occupant at an owner-chosen position | The occupant is a sibling of the content |
|
||||
| `chain` | The elected entry replaces the fallback; with `overlay` it renders beside the fallback | It does not wrap |
|
||||
| Factory local slot | The caller replaces a local Component per occurrence | Only the caller chooses; plugins cannot stack |
|
||||
|
||||
The previous approach had the Conversation classify blank clicks on its body and notify the background plugin through `onBlankClick(listener) => dispose`. The Conversation thereby maintained click classification and subscription forwarding that served only a decorative plugin.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a fifth kind: `hoc`.
|
||||
|
||||
| Item | Contract |
|
||||
|---|---|
|
||||
| Declaration | The parent entry's `children` lists `{ kind: 'hoc', scope }`; `SlotMap` declares `kind: 'hoc'` |
|
||||
| Rendering | `renderSlot(key, owner, { children })`; `entryKey` / `only` / `fallback` / `hookContext` are unavailable; omitted `children` renders the layers around nothing |
|
||||
| Registration | Same as `list`: required `id`, optional `order` and `priority`; the same `id` at the same `priority` throws, a different `priority` shadows |
|
||||
| Nesting order | Ascending `order`, then `priority`, then registration order; lowest outermost |
|
||||
| Layer props | Every layer receives the same owner props plus the framework-supplied `children` (the next inner layer; the innermost is the owner content); `children` is not an owner prop, `HocShare` types it, and owner props must not use that name |
|
||||
| Layer obligation | Render `children` exactly once without creating a box of its own; do not change the props passed inward |
|
||||
| No entry | `children` renders unchanged |
|
||||
| Layer crash | Each layer has its own error boundary; a crashed layer renders its `children` and abdicates, so the next render omits it |
|
||||
| Content error | An error thrown by the wrapped content passes every layer as its original error and is reported against no layer |
|
||||
| Remounting | Adding or removing a layer remounts the content inside it; a `session` or `session-maybe` layer also remounts it when its Session binding changes |
|
||||
|
||||
### Implementation
|
||||
|
||||
| Package | Realization |
|
||||
|---|---|
|
||||
| `ui-slots` | `SlotKind` includes `'hoc'`; `HocRenderOpts`, `HocKeysOf`, `HocShare`; `RenderSlotFn` has a hoc-key overload taking optional `{ children }`; `KindOptions` has a hoc branch; `SlotCore.register` / `entriesOfSlot` key hoc cells by `id` |
|
||||
| `ui-renderer` | `HocOutlet` calls `composeHocLayers` when the slot's ledger version or declaration changes (`useMemo` on the outlet instance) and nests the layers into one render function. Each layer is one Component that fixes its scope-specific Entry (`StrictSessionEntry` / `SessionMaybeEntry` / `RootEntry`), entry, slot inject, and crash reporting at composition, and receives only `owner` and the next inner layer when rendering. With no layer the outlet returns the owner content directly. `HocContentBoundary` wraps each layer's inner content and rethrows its errors as `HocContentError`, which layer boundaries pass through and `HocOutletBoundary` unwraps to the original error. The Entry chain passes `children` to the registered Component as JSX children; non-hoc entries receive `undefined`. `SlotErrorBoundary` takes a `crashed` replacement node |
|
||||
| `scripts/gen-client-catalog.ts` | Register options, a `children`-rendering example, and the `wraps-owner-content` replace risk for hoc slots |
|
||||
|
||||
### First consumer
|
||||
|
||||
| Location | Realization |
|
||||
|---|---|
|
||||
| `ui-conversation` | `conversation.hero.frame` (hoc, root) with owner props `{ hero: boolean }` wraps `.scrollBody` in every phase, so the composer does not remount when the home ends. On the home (`.root[data-phase='hero']`), the Conversation root, which paints the background, sets `isolation: isolate` as part of the frame contract, so layer decorations at negative `z-index` paint above that background and below all Conversation content. The Conversation performs no click classification for its layers |
|
||||
| `ui-brand-official` | `PixelWhale` is a frame layer: a React `onClick` on a `display: contents` frame plays the whale for primary clicks on blank space, excluding portaled overlays (`currentTarget.contains(target)`), controls (a selector), and clicks that end a text selection inside the frame. While `hero` is true it paints a canvas at negative `z-index`. The frame atlas loads through the package asset route; the renderer returns `{ play, dispose }` |
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- `onBlankClick` subscription forwarding: the Conversation owns click classification that serves only a decorative plugin.
|
||||
- Canvas below with `pointer-events: none` on `.scrollBody`: blank space cannot scroll when the home overflows.
|
||||
- A canvas with no `z-index`: it needs no stacking context, but it paints over every unpositioned Conversation content outside the `z-index: 1` home composer stack, such as composer takeovers, and depends on that unrelated rule.
|
||||
- A layer box that establishes the stacking context itself: the layer has to mirror the body's flex column layout.
|
||||
- A separate `renderNestedSlot` or a JSX `<HocSlot>` component: one more set of bindings; `hookContext` already shows that `renderSlot` can constrain its third argument per key.
|
||||
- Precompiling every slot kind per ledger version: no measurable end-to-end time difference, see #7838.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Adding or removing a layer remounts everything inside it, including the composer on the home.
|
||||
- Every non-hoc registered Component receives `children: undefined`, which overrides any owner prop of that name.
|
||||
- Each layer adds one Component and two error boundaries to the tree; each hoc slot with layers adds one outlet boundary.
|
||||
- The outlet's `[data-slot]` element sits between the owner and its content, so owner selectors cannot assume the content is a direct child.
|
||||
- React 18 recollects the component stack where an error is rethrown, so a content error reaching boundaries above a hoc slot with layers carries a component stack that starts at `HocOutletBoundary`.
|
||||
- A layer that wraps its `children` in its own error boundary catches the internal `HocContentError` rather than the original content error.
|
||||
- The frame contract holds only on the home: other phases leave the Conversation root out of the stacking order, so unportaled Conversation popovers still rise above AppFrame chrome that floats over the column, while on the home they stack only within the root.
|
||||
- `HocOutlet` keeps its layer Components through `useMemo`, as Factory local Components keep theirs; if React ever discards memoized values, both need an explicit cache, because these Components are the remount boundary of the content inside them.
|
||||
- [The slot system standard](2026-07-22-slot-type-chain-implementation.md) remains authoritative for the other kinds; this note adds the fifth.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Agent Note: 新增 `hoc` 插槽类型,让插件层层包裹宿主内容
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-03-hoc-slot-kind.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
首页鲸鱼需要在空白处被点击时播放。canvas 必须画在内容下面,`.scrollBody` 铺满整个 body,所以 canvas 自己收不到点击;能收到点击的只有内容的祖先节点。
|
||||
|
||||
现有插槽类型都不能让插件成为内容的祖先:
|
||||
|
||||
| 类型 | 行为 | 为什么不够 |
|
||||
|---|---|---|
|
||||
| `single` / `list` / `keyed` | 在宿主指定位置渲染 occupant | occupant 和内容是兄弟节点 |
|
||||
| `chain` | 选中的 entry 替换 fallback;`overlay` 时与 fallback 并排 | 不包裹 |
|
||||
| Factory 局部 slot | 调用方按次替换局部组件 | 只有调用方能选,插件无法叠加 |
|
||||
|
||||
此前的做法是由 Conversation 在 body 上判定空白点击,再通过 `onBlankClick(listener) => dispose` 通知背景插件。这让 Conversation 维护一套只服务于装饰插件的点击分类和订阅转发。
|
||||
|
||||
## Decision
|
||||
|
||||
新增第五种 kind:`hoc`。
|
||||
|
||||
| 项 | 约定 |
|
||||
|---|---|
|
||||
| 声明 | 父 entry 的 `children` 写 `{ kind: 'hoc', scope }`;`SlotMap` 中 `kind: 'hoc'` |
|
||||
| 渲染 | `renderSlot(key, owner, { children })`;`entryKey` / `only` / `fallback` / `hookContext` 不可用;省略 `children` 时各层包裹空内容 |
|
||||
| 注册 | 与 `list` 相同:必填 `id`,可选 `order`、`priority`;同 `id` 同 `priority` 抛错,不同 `priority` 遮蔽 |
|
||||
| 嵌套顺序 | 依次按 `order`、`priority`、注册顺序升序,最小者在最外层 |
|
||||
| 层的 props | 每层收到同一份 owner props,加上框架注入的 `children`(内一层,最内层是宿主内容);`children` 不属于 owner props,类型由 `HocShare` 提供,owner props 不得使用这个名字 |
|
||||
| 层的义务 | 恰好渲染一次 `children`,自身不产生盒子;不能修改传给内层的 props |
|
||||
| 无 entry | 原样渲染 `children` |
|
||||
| 层崩溃 | 每层有独立 error boundary;崩溃时该层渲染自己的 `children` 并 abdicate,下一次渲染不再包含该层 |
|
||||
| 内容出错 | 被包裹内容抛出的错误以原始错误穿过所有层,不记到任何层名下 |
|
||||
| 重新挂载 | 增减一层会让其内部内容重新挂载;`session` 或 `session-maybe` 作用域的层在 Session binding 变化时也会让内部内容重新挂载 |
|
||||
|
||||
### 实现
|
||||
|
||||
| 包 | 实现 |
|
||||
|---|---|
|
||||
| `ui-slots` | `SlotKind` 包含 `'hoc'`;`HocRenderOpts`、`HocKeysOf`、`HocShare`;`RenderSlotFn` 为 hoc key 提供接受可选 `{ children }` 的重载;`KindOptions` 有 hoc 分支;`SlotCore.register` / `entriesOfSlot` 按 `id` 划分 hoc cell |
|
||||
| `ui-renderer` | `HocOutlet` 在插槽 ledger 版本或声明变化时调用 `composeHocLayers`(`useMemo` 缓存在 outlet 实例上),把各层组合成一个嵌套渲染函数。每层是一个组件,在组合时固定按 scope 选定的 Entry(`StrictSessionEntry` / `SessionMaybeEntry` / `RootEntry`)、entry、slot inject 与崩溃上报,渲染时只接收 `owner` 与内一层。没有层时 outlet 直接返回宿主内容。`HocContentBoundary` 包住每层的内层内容,把其中的错误包装为 `HocContentError` 重新抛出;层的边界放行这种错误,`HocOutletBoundary` 再解开为原始错误。Entry 链以 JSX children 把 `children` 交给注册组件;非 hoc entry 收到 `undefined`。`SlotErrorBoundary` 接受 `crashed` 替换节点 |
|
||||
| `scripts/gen-client-catalog.ts` | hoc 插槽的注册选项、渲染 `children` 的示例,以及 `wraps-owner-content` 替换风险 |
|
||||
|
||||
### 首个使用者
|
||||
|
||||
| 位置 | 实现 |
|
||||
|---|---|
|
||||
| `ui-conversation` | `conversation.hero.frame`(hoc,root),owner props 为 `{ hero: boolean }`,在所有阶段包裹 `.scrollBody`,首页结束时输入框不重新挂载。首页阶段(`.root[data-phase='hero']`),绘制背景的 Conversation 根元素设置 `isolation: isolate`,这是 frame 契约的一部分:层中负 `z-index` 的装饰画在该背景之上、所有 Conversation 内容之下。Conversation 不为层做任何点击判定 |
|
||||
| `ui-brand-official` | `PixelWhale` 是 frame 层:`display: contents` 外框上的 React `onClick` 在主按钮点击留白时播放,排除 portal 浮层(`currentTarget.contains(target)`)、控件(选择器)和结束外框内文字选择的点击。`hero` 为 true 时以负 `z-index` 绘制 canvas。帧图经包 asset 路由加载;渲染器返回 `{ play, dispose }` |
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- `onBlankClick` 订阅转发:Conversation 承担只为装饰插件服务的点击分类。
|
||||
- canvas 放底下、`.scrollBody` 设 `pointer-events: none`:首页溢出时空白处无法滚动。
|
||||
- 不设 `z-index` 的 canvas:不需要层叠上下文,但会画在 `z-index: 1` 的首页输入区以外的所有非定位 Conversation 内容之上(例如输入区接管面板),并依赖那条无关的规则。
|
||||
- 由层自己的盒子建立层叠上下文:层必须复刻 body 的 flex 列布局。
|
||||
- 单独的 `renderNestedSlot` / JSX 组件 `<HocSlot>`:多一套绑定;`hookContext` 已证明可以按 key 约束 `renderSlot` 的第三个参数。
|
||||
- 按 ledger 版本预编译所有插槽类型:端到端测不出耗时差异,见 #7838。
|
||||
|
||||
## Consequences
|
||||
|
||||
- 增减一层会让其内部所有内容重新挂载,包括首页的输入框。
|
||||
- 所有非 hoc 的注册组件都会收到 `children: undefined`,会覆盖同名的 owner prop。
|
||||
- 每层在组件树中多一个组件和两个 error boundary;有层的 hoc 插槽另多一个 outlet 边界。
|
||||
- outlet 的 `[data-slot]` 元素位于 owner 与内容之间,owner 侧选择器不能假定内容是直接子元素。
|
||||
- React 18 会在错误被重新抛出的位置重新采集组件栈,因此有层的 hoc 插槽之上的边界收到的内容错误,组件栈从 `HocOutletBoundary` 开始。
|
||||
- 层如果自己在 `children` 外包错误边界,捕获到的是内部的 `HocContentError`,而不是原始的内容错误。
|
||||
- frame 契约只在首页阶段成立:其他阶段根元素不参与层叠隔离,未经 portal 的 Conversation 浮层仍能盖住悬浮在中栏上方的 AppFrame 元素;首页阶段它们只在根元素内部比较层级。
|
||||
- `HocOutlet` 与 Factory 局部组件一样用 `useMemo` 保持层组件身份;如果 React 将来会丢弃 memo 缓存,两处都需要改成显式缓存,因为这些组件是其内部内容的重新挂载边界。
|
||||
- 其他 kind 仍以 [slot 体系标准](2026-07-22-slot-type-chain-implementation.zh.md) 为准;本 Note 新增第五种。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-11-04-unprefixed-experimental-package-names.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-11-04-unprefixed-experimental-package-names.md
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix:
|
||||
en: b84bbfa015f35105
|
||||
zh: dff2c32694314996
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/problem:
|
||||
en: 84638e34c7b1d291
|
||||
zh: 0c3928b3424db016
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/decision:
|
||||
en: 0f13619da3b05747
|
||||
zh: fac563c2a8b61270
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/alternatives-considered:
|
||||
en: 1c6a08a813dcb3a7
|
||||
zh: 16bf31184377c80b
|
||||
/agent-note-name-new-experimental-packages-without-the-experimental-prefix/consequences:
|
||||
en: 2b536c42672e8850
|
||||
zh: f6a551964855308e
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Name new experimental packages without the experimental prefix
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-11-04-unprefixed-experimental-package-names.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
An experimental package named `@deepseek-ai/dsh-experimental-<name>` must be renamed when it moves to a product group. The rename changes every import, dependency entry, module declaration, and profile file that names the package, so promotion carries a migration that is unrelated to the capability's behavior.
|
||||
|
||||
## Decision
|
||||
|
||||
New experimental packages use `@deepseek-ai/dsh-<name>` and never the `experimental` prefix. Each one is declared in `EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS` in the [experimental package policy](../../../../scripts/experimental-package-policy.ts), because isolation checks and plugin-record ownership recognize an unprefixed experimental name only through that map. Promotion moves the package and removes its map entry; its npm name stays the same.
|
||||
|
||||
Existing packages with the prefix are listed in `LEGACY_PREFIXED_EXPERIMENTAL_PACKAGE_DIRECTORIES`. `pnpm run constraints` rejects the prefix in any other directory and rejects a listed directory that no longer holds a prefixed package, so the list can only shrink. A legacy package is renamed when it is promoted; renaming it earlier needs the release and migration evidence that the [plugin identity decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) requires.
|
||||
|
||||
This decision replaces the deferred cleanup that the plugin identity decision recorded, which gave every experimental package the prefix. The [package rules](../../../../packages/experimental/AGENTS.md) state the naming rule for authors.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the prefix as the default.** The name shows experimental status, but each promotion then renames the package and migrates its consumers and installed profiles.
|
||||
|
||||
**Rename the legacy packages now.** One rename removes the legacy list, but it changes published names that existing dependencies and profiles use, without a promotion that needs the change.
|
||||
|
||||
**Document the rule without a gate.** A new package copied from a legacy one would keep the prefix with every check passing.
|
||||
|
||||
## Consequences
|
||||
|
||||
The unprefixed-name map grows with every new experimental package; the legacy list only shrinks. Directory placement and the map, not the npm name, identify an unprefixed package as experimental, and an unprefixed name does not grant admission to the default product.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Agent Note: 新实验包命名不使用 experimental 前缀
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-11-04-unprefixed-experimental-package-names.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
名为 `@deepseek-ai/dsh-experimental-<name>` 的实验包迁入产品分组时必须重命名。重命名会改变引用该包名的每个导入、依赖项、模块声明和 profile 文件,因此晋升会附带一次与能力行为无关的迁移。
|
||||
|
||||
## 决策
|
||||
|
||||
新实验包使用 `@deepseek-ai/dsh-<name>`,不使用 `experimental` 前缀。每个新实验包都在[实验包命名策略](../../../../scripts/experimental-package-policy.ts)的 `EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS` 中声明,因为隔离检查和插件记录归属只通过这个映射识别不带前缀的实验包名。晋升时移动该包并删除其映射条目;其 npm 名称保持不变。
|
||||
|
||||
带前缀的既有包列在 `LEGACY_PREFIXED_EXPERIMENTAL_PACKAGE_DIRECTORIES` 中。`pnpm run constraints` 拒绝其他目录中出现该前缀,也拒绝已列出但不再包含带前缀包的目录,因此该清单只能缩减。遗留包在晋升时重命名;提前重命名需要[插件身份决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)要求的发布与迁移依据。
|
||||
|
||||
本决策取代插件身份决策记录的暂缓清理,该清理为每个实验包加上前缀。[包规则](../../../../packages/experimental/AGENTS.md)向作者说明命名规则。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**默认保留前缀。** 名称能体现实验状态,但每次晋升都要重命名该包,并迁移其消费方和已安装 profile。
|
||||
|
||||
**现在重命名遗留包。** 一次重命名即可删除遗留清单,但会在没有晋升需要的情况下改变既有依赖和 profile 使用的已发布名称。
|
||||
|
||||
**只记录规则,不设门禁。** 从遗留包复制的新包会保留前缀,而所有检查仍会通过。
|
||||
|
||||
## 后果
|
||||
|
||||
不带前缀包名映射随每个新实验包增长;遗留清单只会缩减。识别不带前缀的包是否为实验包依据的是目录位置和该映射,而不是 npm 名称;不带前缀的名称也不因此获准进入默认产品。
|
||||
@@ -12,8 +12,8 @@
|
||||
en: 47b44ed9313c2ccf
|
||||
zh: 7ad254f8ef88351d
|
||||
/agent-note-subagent-end-turn-guidance-stays-in-the-delegation-paragraph/alternatives-considered:
|
||||
en: d52fa1af379dea5b
|
||||
zh: 4e404994f7a607dc
|
||||
en: 3153c1e7563a3357
|
||||
zh: 9841db2b0156075b
|
||||
/agent-note-subagent-end-turn-guidance-stays-in-the-delegation-paragraph/verification:
|
||||
en: be076216381ea64d
|
||||
zh: 2da8d9819c20e2fe
|
||||
|
||||
@@ -29,7 +29,7 @@ The sentence stays one literal line. Rewording `the next result starts a new tur
|
||||
|
||||
**Move the guidance into the tool description.** The paragraph is assembled from the set of visible delegation tools and lists their names; the tool description describes one tool. Moving the sentence changes ownership without changing the behavior it prevents.
|
||||
|
||||
**Restore every prompt phrase reverted together with this sentence.** The read guidance and the `run_code` argument order are separate decisions with their own evidence; this note records only the delegation paragraph.
|
||||
**Restore every prompt phrase reverted together with this sentence.** The read guidance ([2026-10-26 note](2026-10-26-read-guidance-line-ranges.md)) and the `run_code` argument order are separate decisions with their own evidence; this note records only the delegation paragraph.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Status: implemented
|
||||
|
||||
**把说明移进工具描述。** 这段指导按可见委派工具集合组装并列出工具名,工具描述只描述单个工具。移动这句会改变归属,但不改变它阻止的行为。
|
||||
|
||||
**把与这句一起被回退的所有提示词都恢复。** read 指导与 `run_code` 参数顺序是各自有证据的独立决定;本记录只覆盖委派段。
|
||||
**把与这句一起被回退的所有提示词都恢复。** read 指导([2026-10-26 note](2026-10-26-read-guidance-line-ranges.zh.md))与 `run_code` 参数顺序是各自有证据的独立决定;本记录只覆盖委派段。
|
||||
|
||||
## 验证
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Bilingual-pair consistency record for 2026-10-26-read-guidance-line-ranges.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-10-26-read-guidance-line-ranges.md
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges:
|
||||
en: 1926710614598539
|
||||
zh: b0106d4a6a2d648d
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges/problem:
|
||||
en: 4189d516419d8d12
|
||||
zh: 30d8496064dd44e3
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges/decision:
|
||||
en: 34f18307f7ad87af
|
||||
zh: 21c466c6ce6c598d
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges/alternatives-considered:
|
||||
en: aea7ffb1159ab81a
|
||||
zh: 4842ca79dc30654d
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges/verification:
|
||||
en: cbc16bfba4abe862
|
||||
zh: 9a71e6c020c01f69
|
||||
/agent-note-read-guidance-names-shell-readers-and-line-ranges/consequences:
|
||||
en: cf55161ea197a7a9
|
||||
zh: fa10ca413f8fd726
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: Read guidance names shell readers and line ranges
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-26-read-guidance-line-ranges.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The read guidance named only `cat` as the shell command the read tool replaces, and described `offset`/`limit` as continuing a large file rather than reading a line range. #6057 recorded a long Session reading ranges with `sed -n X,Yp` 60 times. #6054 added `sed`, `head`, `tail`, and the line-range wording; a later prompt revert removed them together with unrelated changes.
|
||||
|
||||
The 2026-10-23 evaluation of the SDK profile (native tools, six benchmarks) measured this sentence as the only change: Pass@1 mean −0.03pp and Pass@3 mean +0.70pp over the five benchmarks that report Pass@3 (ProgramBench-New has none) versus the base commit, with mixed step and token deltas (ProgramBench-New −15.5 steps, Terminal-Bench-v4 +13.5 steps). The PTC mode runs did not include this variant; the section renders in every request where `read` is visible.
|
||||
|
||||
## Decision
|
||||
|
||||
Restore the #6054 wording, byte-identical: `Use the read tool — not shell commands like cat, sed, head, or tail — to inspect text files. Use offset and limit to read a line range or continue a large file.` The read tool and its guidance are owned by `@deepseek-ai/dsh-tool-fs`; write and edit guidance are unchanged.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Rely on the read tool schema.** The schema already documents `offset` and `limit`, but #6057 shows the model chose `sed` while that schema existed; the guidance is the only model-visible place that names `sed`, `head`, and `tail`.
|
||||
|
||||
**Restore every prompt phrase reverted together with this sentence.** The subagent end-turn sentence, the `run_code` argument order, the PTC syntax ownership, and the SDK persona are separate decisions; the first is already restored ([subagent end-turn note](2026-10-21-subagent-end-turn-guidance.md)), and the others carry their own evidence.
|
||||
|
||||
**Say "line range" without naming shell commands.** The observed failure was choosing shell commands, so the sentence names them.
|
||||
|
||||
## Verification
|
||||
|
||||
- `packages/fs/tool-fs/tests/tools.spec.ts` asserts the rendered guidance text.
|
||||
- Every recorded `system-prompt*.expected.md` sidecar that renders the read guidance carries the restored sentence: `test:snapshot` replays the session and SDK set, and `pnpm run test:web:ci` replays the `snapshots/web` set (the `node 24 / snapshots and artifacts` lane runs both). Two pinned historical sidecars (`web/cordis-tool-round`, `web/schedule-catalog`) retain their recorded text.
|
||||
- The evaluation above measured this single change on the native SDK profile.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every request with the read tool carries three more command names and a line-range clause, about 10 tokens.
|
||||
- The model is directed to the read tool for line ranges instead of `cat`, `sed`, `head`, or `tail`.
|
||||
- The benchmark measured this sentence as neutral; keeping it rests on the #6057 production observation, not on a measured quality gain.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Note: read 指导点名 shell 读取命令与行区间
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-26-read-guidance-line-ranges.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
read 指导只把 `cat` 点名为 read 工具替代的 shell 命令,并把 `offset`/`limit` 描述成继续读大文件,而不是读行区间。#6057 记录了一个长 Session 用 `sed -n X,Yp` 读行区间 60 次。#6054 增加了 `sed`、`head`、`tail` 和行区间措辞;后来的一次提示词回退把它们和不相关的改动一起删掉了。
|
||||
|
||||
2026-10-23 对 SDK profile(native 工具、六个 benchmark)的评测里,这句是唯一改动:相对 base commit,Pass@1 均值 −0.03pp、Pass@3 均值 +0.70pp(该均值取报告 Pass@3 的五个 benchmark;ProgramBench-New 没有 Pass@3),步数与 token 变化方向不一(ProgramBench-New −15.5 步、Terminal-Bench-v4 +13.5 步)。PTC 模式的运行没有包含这个变体;只要 `read` 可见,这段就会进入每个请求。
|
||||
|
||||
## 决策
|
||||
|
||||
逐字恢复 #6054 的措辞:`Use the read tool — not shell commands like cat, sed, head, or tail — to inspect text files. Use offset and limit to read a line range or continue a large file.` read 工具及其指导归 `@deepseek-ai/dsh-tool-fs` 所有;write 与 edit 的指导不变。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**只依赖 read 工具 schema。** schema 已经写明 `offset` 与 `limit`,但 #6057 显示在那个 schema 存在时模型仍选择 `sed`;指导是唯一点名 `sed`、`head`、`tail` 的模型可见位置。
|
||||
|
||||
**把与这句一起被回退的所有提示词都恢复。** subagent 结束本轮句、`run_code` 参数顺序、PTC 语法归属、SDK persona 是各自独立的决定;第一项已经恢复([subagent 结束本轮 note](2026-10-21-subagent-end-turn-guidance.zh.md)),其余各有自己的证据。
|
||||
|
||||
**只说“行区间”,不点名 shell 命令。** 观测到的失败就是模型选择 shell 命令,所以这句点名它们。
|
||||
|
||||
## 验证
|
||||
|
||||
- `packages/fs/tool-fs/tests/tools.spec.ts` 断言渲染出的指导文本。
|
||||
- 所有渲染 read 指导的 `system-prompt*.expected.md` sidecar 都带有恢复后的句子:`test:snapshot` 回放 session 与 SDK 集合,`pnpm run test:web:ci` 回放 `snapshots/web` 集合(`node 24 / snapshots and artifacts` lane 两条都跑)。两个显式 pin 的历史 sidecar(`web/cordis-tool-round`、`web/schedule-catalog`)保留录制文本。
|
||||
- 上面的评测在 native SDK profile 上只改了这一句进行测量。
|
||||
|
||||
## 影响
|
||||
|
||||
- 只要 read 工具存在,每个请求多三个命令名和一句行区间说明,约 10 token。
|
||||
- 模型读行区间时被引导到 read 工具,而不是 `cat`、`sed`、`head`、`tail`。
|
||||
- benchmark 上这句测得为中性;保留它依据的是 #6057 的生产观测,而不是测到的质量收益。
|
||||
Loaded 100 of 2559 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user