@nelsonlongxiang/dsh-open-a2a-net 0.6.38 → 0.6.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +340 -335
- package/README_zh.md +241 -236
- package/assets/nexusDist/assets/index-C7HagaGy.css +1 -1
- package/assets/nexusDist/assets/index-NkNSeIx6.js +9 -9
- package/cli/a2a.mjs +12 -4
- package/cli/serve.mjs +132 -0
- package/cordis.patch.yml +21 -21
- package/lib/canvas-id.js +27 -0
- package/lib/canvas-store.js +7 -1
- package/lib/client.js +63 -63
- package/lib/config.js +125 -0
- package/lib/direct-forward.js +64 -18
- package/lib/faces/admin.js +4 -1
- package/lib/faces/wire.js +24 -6
- package/lib/flood-guard.js +3 -1
- package/lib/index.js +43 -145
- package/lib/live-roots.js +39 -0
- package/lib/origin-claim.js +3 -2
- package/lib/queue-guard.js +3 -1
- package/lib/skill/references/cli.md +214 -203
- package/lib/skill/references/routing.md +4 -0
- package/lib/task-auth.js +14 -10
- package/lib/types/canvas-id.d.ts +9 -0
- package/lib/types/config.d.ts +278 -0
- package/lib/types/direct-forward.d.ts +25 -3
- package/lib/types/faces/admin.d.ts +2 -0
- package/lib/types/faces/wire.d.ts +7 -0
- package/lib/types/index.d.ts +3 -277
- package/lib/types/live-roots.d.ts +35 -0
- package/lib/types/origin-claim.d.ts +1 -0
- package/lib/types/queue-guard.d.ts +2 -0
- package/lib/types/task-auth.d.ts +3 -0
- package/lib/types/types.d.ts +2 -0
- package/nexus-stage/package-lock.json +2262 -2262
- package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
- package/nexus-stage/src/edges.ts +72 -72
- package/nexus-stage/src/layout-wire.ts +158 -158
- package/nexus-stage/src/seat.ts +35 -35
- package/nexus-stage/src/stage-name.ts +13 -13
- package/nexus-stage/src/tokens.ts +31 -31
- package/package.json +218 -218
package/cli/a2a.mjs
CHANGED
|
@@ -46,6 +46,7 @@ usage: a2a <command> [args] [--home URL] [--key <apiKey>] [--json]
|
|
|
46
46
|
户口制: init <name> [--uuid 16hex] [--force] · publish · inbox [--new|--wait|--hook] · revoke
|
|
47
47
|
对齐面: whoami · teams [query] · route <team> <msg…> [--sync] [--context ID] [--task ID] · status · tasks · probe · report <msg…> [--task ID]
|
|
48
48
|
CLI 专有: skills [name] [--json] [--root DIR] · tools (this alignment table) · help
|
|
49
|
+
独立节点: serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port N] [--home DIR] (the network layer without a DSH instance)
|
|
49
50
|
文件通道: file send <path…> <team> [--note TEXT] · file fetch <ref> [--out DIR] (#91: bytes never ride a message)
|
|
50
51
|
|
|
51
52
|
host: --home > $A2A_HOME > the home saved by 'a2a init --home URL' > ${DEFAULT_HOME}
|
|
@@ -108,7 +109,8 @@ function parseArgs(argv) {
|
|
|
108
109
|
return { flags, positional }
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
// `serve` owns its flags (cli/serve.mjs) and loads the node runtime lazily: the other commands never need it.
|
|
113
|
+
const parsed = process.argv[2] === 'serve' ? { flags: new Map(), positional: ['serve'] } : parseArgs(process.argv.slice(2))
|
|
112
114
|
const cmd = parsed.positional[0] ?? ''
|
|
113
115
|
const flag = (name) => {
|
|
114
116
|
const value = parsed.flags.get(name)
|
|
@@ -868,7 +870,8 @@ async function printDelivery(verb, res) {
|
|
|
868
870
|
if (out.task_status !== undefined && out.task_status !== 'TASK_STATE_COMPLETED') console.error(`[${verb}] status ${String(out.task_status)} — the round has not settled; reconcile by task id before re-sending`)
|
|
869
871
|
return
|
|
870
872
|
}
|
|
871
|
-
|
|
873
|
+
// #282 S1: one actionable next step under the reason, when the host knows one.
|
|
874
|
+
if (out.delivered !== true) die(`[${verb}] not delivered (HTTP ${String(res.status)}): ${String(out.error ?? out.task_status ?? 'no reason given')}${typeof out.next === 'string' && out.next !== '' ? `\n[${verb}] next: ${out.next}` : ''}`)
|
|
872
875
|
const receipt = out.receipt === 'none' ? 'no receipt will come' : 'receipt arrives in: a2a inbox'
|
|
873
876
|
console.log(`[${verb}] delivered → ${String(out.team)} · task ${String(out.task_id)} · ${receipt}`)
|
|
874
877
|
}
|
|
@@ -877,18 +880,23 @@ async function printDelivery(verb, res) {
|
|
|
877
880
|
function stateCanvasTeams(s) {
|
|
878
881
|
const rows = []
|
|
879
882
|
const owner = String(s.ownerHost ?? home())
|
|
880
|
-
for (const team of s.canvas?.teams ?? []) rows.push({ team: String(team.team ?? String(s.zone ?? 'dsh') + '/canvas/' + team.name), name: String(team.name), kind: 'canvas', owner, memberCount: (team.members ?? []).length, where: 'local canvas', members: (team.members ?? []).map(m => typeof m === 'string' ? m : m.team ?? m.id) })
|
|
883
|
+
for (const team of s.canvas?.teams ?? []) rows.push({ team: String(team.id ?? team.team ?? String(s.zone ?? 'dsh') + '/canvas/' + team.name), name: String(team.name), kind: 'canvas', owner, memberCount: (team.members ?? []).length, where: 'local canvas', members: (team.members ?? []).map(m => typeof m === 'string' ? m : m.team ?? m.id) })
|
|
881
884
|
const seen = new Set()
|
|
882
885
|
for (const peer of s.peerOrg ?? []) for (const team of peer.org ?? []) {
|
|
883
886
|
const key = String(peer.url) + ':' + String(team.team)
|
|
884
887
|
if (seen.has(key)) continue
|
|
885
888
|
seen.add(key)
|
|
886
|
-
rows.push({ team: String(team.team), name: String(team.name), kind: 'canvas', owner: String(peer.url), memberCount: (team.members ?? []).length, where: 'remote canvas', members: team.members ?? [] })
|
|
889
|
+
rows.push({ team: String(team.id || team.team), name: String(team.name), kind: 'canvas', owner: String(peer.url), memberCount: (team.members ?? []).length, where: 'remote canvas', members: team.members ?? [] })
|
|
887
890
|
}
|
|
888
891
|
return rows
|
|
889
892
|
}
|
|
890
893
|
|
|
891
894
|
async function main() {
|
|
895
|
+
if (cmd === 'serve') {
|
|
896
|
+
const { serve } = await import('./serve.mjs')
|
|
897
|
+
await serve(process.argv.slice(3))
|
|
898
|
+
return
|
|
899
|
+
}
|
|
892
900
|
if (cmd === 'help' || has('--help') || has('-h')) {
|
|
893
901
|
console.log(USAGE)
|
|
894
902
|
return
|
package/cli/serve.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `a2a-net serve` — the A2A network layer as a standalone node, without a DSH instance
|
|
3
|
+
* (owner request 2026-11-04: the network must not go down when a DSH host restarts).
|
|
4
|
+
*
|
|
5
|
+
* It boots the same plugin a DSH host runs, in a bare cordis context with only the four
|
|
6
|
+
* services the network layer needs (system prompt, tool runtime, timer, web server) and no
|
|
7
|
+
* sessions: cards and OriginClaim, the peer directory, CLI deposits and mailboxes, one-hop
|
|
8
|
+
* forwarding, canvas ownership, the task ledger, the state face and the workbench. Delivering
|
|
9
|
+
* to an AI session still needs a DSH host — this node forwards there like any home host.
|
|
10
|
+
*
|
|
11
|
+
* a2a-net serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port 13090] [--home DIR]
|
|
12
|
+
*
|
|
13
|
+
* node.json holds plugin Config keys (team, session, agentName, apiKey, peers,
|
|
14
|
+
* originClaimUrl, originClaimAudiences, forwardDirect, …); flags win over the file.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync, mkdirSync } from 'node:fs'
|
|
17
|
+
import { homedir, hostname } from 'node:os'
|
|
18
|
+
import { join, resolve } from 'node:path'
|
|
19
|
+
|
|
20
|
+
/** The tested runtime set (the repo lockfile's versions); npm's `latest` tags are older and do not work. */
|
|
21
|
+
export const SERVE_DEPS = {
|
|
22
|
+
'@deepseek-ai/cordis': '4.0.5',
|
|
23
|
+
'@deepseek-ai/cordis-plugin-timer': '1.1.7',
|
|
24
|
+
'@deepseek-ai/dsh-host-webserver': '0.1.7-rc.2',
|
|
25
|
+
'@deepseek-ai/dsh-tools': '0.1.7-rc.2',
|
|
26
|
+
'@deepseek-ai/dsh-system-prompt': '0.1.7-rc.2',
|
|
27
|
+
'@deepseek-ai/dsh-home-paths': '0.1.7-rc.2',
|
|
28
|
+
'@deepseek-ai/dsh-llm': '0.1.7-rc.2',
|
|
29
|
+
'@deepseek-ai/dsh-session': '0.1.7-rc.2',
|
|
30
|
+
'@deepseek-ai/schemastery': '3.18.5',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const SERVE_USAGE = 'usage: a2a-net serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port 13090] [--home DIR]'
|
|
34
|
+
|
|
35
|
+
/** One `npm i` line that installs this package and the tested runtime set into a folder. */
|
|
36
|
+
export function installLine(version) {
|
|
37
|
+
return 'npm i @nelsonlongxiang/dsh-open-a2a-net@' + version + ' ' + Object.entries(SERVE_DEPS).map(([name, v]) => name + '@' + v).join(' ')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Merge file config and flags into the plugin config; pure. */
|
|
41
|
+
export function nodeConfig(file, flags) {
|
|
42
|
+
const raw = flags.port ?? file.port ?? 13090
|
|
43
|
+
const port = typeof raw === 'number' ? raw : /^\d+$/.test(String(raw)) ? Number(raw) : NaN
|
|
44
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('--port must be an integer 0-65535')
|
|
45
|
+
const home = resolve(flags.home ?? file.dshHome ?? join(homedir(), '.a2a-net', 'node'))
|
|
46
|
+
const { port: _port, host: _host, ...rest } = file
|
|
47
|
+
void _port; void _host
|
|
48
|
+
return {
|
|
49
|
+
host: flags.host ?? file.host ?? '127.0.0.1',
|
|
50
|
+
port,
|
|
51
|
+
config: {
|
|
52
|
+
team: 'dsh',
|
|
53
|
+
agentName: 'a2a-net node',
|
|
54
|
+
session: 'a2a-node-' + hostname().toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 24),
|
|
55
|
+
apiKey: '',
|
|
56
|
+
peers: [],
|
|
57
|
+
announce: true,
|
|
58
|
+
...rest,
|
|
59
|
+
// The control faces (state, CLI deposits and mailboxes, canvas) are mounted with session nodes; this node
|
|
60
|
+
// composes no agents service, so there is no session to track, expose or wake.
|
|
61
|
+
sessionNodes: true,
|
|
62
|
+
wakeJoinedOnBoot: false,
|
|
63
|
+
dshHome: home,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Boot the node; resolves to { url, close }. `load` is injectable for tests. */
|
|
69
|
+
export async function startNode({ host, port, config }, load = specifier => import(specifier)) {
|
|
70
|
+
const [{ Context }, timer, webServer, tools, systemPrompt, plugin] = await Promise.all([
|
|
71
|
+
load('@deepseek-ai/cordis'),
|
|
72
|
+
load('@deepseek-ai/cordis-plugin-timer'),
|
|
73
|
+
load('@deepseek-ai/dsh-host-webserver'),
|
|
74
|
+
load('@deepseek-ai/dsh-tools'),
|
|
75
|
+
load('@deepseek-ai/dsh-system-prompt'),
|
|
76
|
+
load(new URL('../lib/index.js', import.meta.url).href),
|
|
77
|
+
])
|
|
78
|
+
mkdirSync(config.dshHome, { recursive: true })
|
|
79
|
+
const ctx = new Context()
|
|
80
|
+
await ctx.plugin(systemPrompt.default)
|
|
81
|
+
await ctx.plugin(tools.default)
|
|
82
|
+
await ctx.plugin(timer.default)
|
|
83
|
+
await ctx.plugin(webServer.default, { host, port })
|
|
84
|
+
try {
|
|
85
|
+
plugin.apply(ctx, plugin.Config(config))
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await ctx.fiber.dispose() // the web server is already listening
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
const bound = ctx.webServer.port
|
|
91
|
+
const url = 'http://' + (host === '0.0.0.0' ? '127.0.0.1' : host) + ':' + String(bound)
|
|
92
|
+
return { url, port: bound, close: async () => { await ctx.fiber.dispose() } }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** CLI entry: parse argv, boot, log one line, stay up until SIGINT/SIGTERM. */
|
|
96
|
+
export async function serve(argv) {
|
|
97
|
+
const version = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
|
|
98
|
+
const flags = {}
|
|
99
|
+
if (argv.includes('--help') || argv.includes('-h')) { console.log(SERVE_USAGE); process.exit(0) }
|
|
100
|
+
for (let i = 0; i < argv.length; i++) {
|
|
101
|
+
const eq = argv[i].indexOf('=')
|
|
102
|
+
const name = eq === -1 ? argv[i] : argv[i].slice(0, eq)
|
|
103
|
+
const inline = eq === -1 ? undefined : argv[i].slice(eq + 1)
|
|
104
|
+
if (!['--config', '--host', '--port', '--home'].includes(name)) { console.error(SERVE_USAGE); process.exit(2) }
|
|
105
|
+
const value = inline ?? argv[++i]
|
|
106
|
+
if (value === undefined || value === '' || value.startsWith('--')) { console.error(SERVE_USAGE); process.exit(2) }
|
|
107
|
+
flags[name.slice(2)] = value
|
|
108
|
+
}
|
|
109
|
+
let file = {}
|
|
110
|
+
if (flags.config !== undefined) {
|
|
111
|
+
try { file = JSON.parse(readFileSync(flags.config, 'utf8')) } catch (error) { console.error('[serve] cannot read --config ' + flags.config + ': ' + error.message); process.exit(2) }
|
|
112
|
+
if (file === null || typeof file !== 'object' || Array.isArray(file)) { console.error('[serve] --config must hold a JSON object of plugin config keys'); process.exit(2) }
|
|
113
|
+
}
|
|
114
|
+
let options
|
|
115
|
+
try { options = nodeConfig(file, flags) } catch (error) { console.error('[serve] ' + error.message); process.exit(2) }
|
|
116
|
+
let node
|
|
117
|
+
try {
|
|
118
|
+
node = await startNode(options)
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error?.code === 'ERR_MODULE_NOT_FOUND') {
|
|
121
|
+
console.error('[serve] a runtime dependency is missing: ' + error.message.split('\n')[0])
|
|
122
|
+
console.error('[serve] install the tested set into a folder and run from there:\n ' + installLine(version) + '\n npx a2a-net serve --config node.json')
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
console.error('[serve] failed to start: ' + (error?.stack ?? error))
|
|
126
|
+
process.exit(1)
|
|
127
|
+
}
|
|
128
|
+
console.log('[serve] a2a-net node on ' + node.url + ' · zone ' + options.config.team + ' · session ' + options.config.session + ' · home ' + options.config.dshHome + ' (no DSH sessions; Ctrl+C to stop)')
|
|
129
|
+
const stop = async () => { await node.close().catch(() => {}); process.exit(0) }
|
|
130
|
+
process.once('SIGINT', stop)
|
|
131
|
+
process.once('SIGTERM', stop)
|
|
132
|
+
}
|
package/cordis.patch.yml
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# Bundle patch for @nelsonlongxiang/dsh-open-a2a-net: mounts the a2a row into
|
|
2
|
-
# the host composition of a dsh profile. The plugin registers its
|
|
3
|
-
# /.well-known/agent-card.json announce route, its /__dsh_a2a/* state and
|
|
4
|
-
# control routes, and the a2a_teams / a2a_route model tools on the shared web
|
|
5
|
-
# server and tool runtime — it owns no listener of its own, so compose it
|
|
6
|
-
# beside a webserver row (the stock web profile already has one).
|
|
7
|
-
#
|
|
8
|
-
# Install: `dsh plugin --profile <name> add @nelsonlongxiang/dsh-open-a2a-net`
|
|
9
|
-
# (npm, a local path, or Git). The `dsh plugin` command pnpm-installs the
|
|
10
|
-
# package into the profile and reconciles it into the profile's
|
|
11
|
-
# `dsh.profile.bundles` layer list.
|
|
12
|
-
#
|
|
13
|
-
# All config keys carry schema defaults: session nodes are on (the sidebar
|
|
14
|
-
# join control works out of the box), announce is opt-in. Peers, zone
|
|
15
|
-
# delegates, and an API key are set per deployment through row config
|
|
16
|
-
# overlays, never here.
|
|
17
|
-
- insert:
|
|
18
|
-
- id: a2a
|
|
19
|
-
# Node-resolvable package name — must stay in sync with package.json
|
|
20
|
-
# `name`. Quoted because `@` is a reserved YAML indicator.
|
|
21
|
-
name: '@nelsonlongxiang/dsh-open-a2a-net'
|
|
1
|
+
# Bundle patch for @nelsonlongxiang/dsh-open-a2a-net: mounts the a2a row into
|
|
2
|
+
# the host composition of a dsh profile. The plugin registers its
|
|
3
|
+
# /.well-known/agent-card.json announce route, its /__dsh_a2a/* state and
|
|
4
|
+
# control routes, and the a2a_teams / a2a_route model tools on the shared web
|
|
5
|
+
# server and tool runtime — it owns no listener of its own, so compose it
|
|
6
|
+
# beside a webserver row (the stock web profile already has one).
|
|
7
|
+
#
|
|
8
|
+
# Install: `dsh plugin --profile <name> add @nelsonlongxiang/dsh-open-a2a-net`
|
|
9
|
+
# (npm, a local path, or Git). The `dsh plugin` command pnpm-installs the
|
|
10
|
+
# package into the profile and reconciles it into the profile's
|
|
11
|
+
# `dsh.profile.bundles` layer list.
|
|
12
|
+
#
|
|
13
|
+
# All config keys carry schema defaults: session nodes are on (the sidebar
|
|
14
|
+
# join control works out of the box), announce is opt-in. Peers, zone
|
|
15
|
+
# delegates, and an API key are set per deployment through row config
|
|
16
|
+
# overlays, never here.
|
|
17
|
+
- insert:
|
|
18
|
+
- id: a2a
|
|
19
|
+
# Node-resolvable package name — must stay in sync with package.json
|
|
20
|
+
# `name`. Quoted because `@` is a reserved YAML indicator.
|
|
21
|
+
name: '@nelsonlongxiang/dsh-open-a2a-net'
|
package/lib/canvas-id.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner-qualified canvas ids (#282 S2, design docs/design/simplify-2026-11.md D1).
|
|
3
|
+
* `<zone>/canvas/<name>@<fp8>`: the suffix is the first 8 hex of sha256 over the
|
|
4
|
+
* owner's signed card public key (SPKI DER), so a host that changes address keeps
|
|
5
|
+
* its id. The suffix selects a signing key, not a URL: as on master, a seeded peer
|
|
6
|
+
* serving that key's signed card core counts as an alias of the same owner, and the
|
|
7
|
+
* card's canvasOrg itself is not signed. A bare `<zone>/canvas/<name>` keeps routing
|
|
8
|
+
* exactly as before; the suffix only selects among owners.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
const QUALIFIED = /^(.+\/canvas\/[^/]+)@([0-9a-f]{8})$/;
|
|
12
|
+
/** First 8 hex of sha256 over the base64 SPKI DER public key; '' when there is no key. */
|
|
13
|
+
export function canvasOwnerFingerprint(publicKey) {
|
|
14
|
+
if (publicKey === undefined || publicKey === '')
|
|
15
|
+
return '';
|
|
16
|
+
return createHash('sha256').update(Buffer.from(publicKey, 'base64')).digest('hex').slice(0, 8);
|
|
17
|
+
}
|
|
18
|
+
/** Split `<zone>/canvas/<name>@<fp8>` into the plain team and the owner fingerprint. */
|
|
19
|
+
export function splitCanvasOwner(team) {
|
|
20
|
+
const m = QUALIFIED.exec(team);
|
|
21
|
+
return m === null ? { team } : { team: m[1], owner: m[2] };
|
|
22
|
+
}
|
|
23
|
+
/** The display/routing id of a canvas team owned by the card holding `publicKey`. */
|
|
24
|
+
export function qualifiedCanvasId(team, publicKey) {
|
|
25
|
+
const fp = canvasOwnerFingerprint(publicKey);
|
|
26
|
+
return fp === '' ? team : `${team}@${fp}`;
|
|
27
|
+
}
|
package/lib/canvas-store.js
CHANGED
|
@@ -56,6 +56,12 @@ function validName(raw) {
|
|
|
56
56
|
return undefined;
|
|
57
57
|
return clean;
|
|
58
58
|
}
|
|
59
|
+
/** A NEW canvas name: `@<8 lowercase hex>` at the end is the owner-fingerprint suffix of a routing id (#282 S2), never part of a name.
|
|
60
|
+
* An existing canvas with such a name still loads; it is reached by its full id `<zone>/canvas/<name>@<own fp8>`. */
|
|
61
|
+
function validNewName(raw) {
|
|
62
|
+
const clean = validName(raw);
|
|
63
|
+
return clean === undefined || /@[0-9a-f]{8}$/.test(clean) ? undefined : clean;
|
|
64
|
+
}
|
|
59
65
|
/**
|
|
60
66
|
* Bounded canvas-team store with last-write-wins persistence. Pure of any
|
|
61
67
|
* registry knowledge: callers validate join state and resolve members.
|
|
@@ -191,7 +197,7 @@ export class CanvasStore {
|
|
|
191
197
|
* @returns the stored name, or undefined when rejected.
|
|
192
198
|
*/
|
|
193
199
|
create(name) {
|
|
194
|
-
const clean =
|
|
200
|
+
const clean = validNewName(name);
|
|
195
201
|
if (clean === undefined)
|
|
196
202
|
return undefined;
|
|
197
203
|
if (this.hasTeam(clean))
|
package/lib/client.js
CHANGED
|
@@ -22,8 +22,8 @@ window.__ModuleLoader__.load({
|
|
|
22
22
|
return n;
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
|
-
//#region \0dsh-css:D:\workspace\dsh-open-a2a-net\
|
|
26
|
-
const css = ".
|
|
25
|
+
//#region \0dsh-css:D:\workspace\dsh-open-a2a-net\src\client\A2aControl.module.css.mjs
|
|
26
|
+
const css = ".Q9Ky_q_wrap{max-width:100%;display:inline-flex;position:relative}.Q9Ky_q_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.Q9Ky_q_unreadBadge{text-align:center;color:#fff;background:var(--dsh-accent,#4c8bf5);border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;line-height:14px;animation:2s ease-in-out infinite Q9Ky_q_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes Q9Ky_q_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_unreadBadge{animation:none}}.Q9Ky_q_popover{z-index:1100;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface-raised,#1c1c22);border-radius:10px;flex-direction:column;gap:8px;width:320px;max-width:calc(100vw - 16px);max-height:60vh;padding:12px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000059}.Q9Ky_q_title{font-size:13px;font-weight:600}.Q9Ky_q_sectionTitle{border-top:1px solid var(--dsh-border,#2a2a30);text-transform:uppercase;letter-spacing:.04em;opacity:.65;margin-top:4px;padding-top:8px;font-size:11px;font-weight:600}.Q9Ky_q_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.Q9Ky_q_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.Q9Ky_q_stageLinks{gap:6px;display:flex}.Q9Ky_q_stageLink{border:1px solid var(--dsh-border,#2a2a30);color:inherit;border-radius:999px;align-items:center;padding:3px 8px;font-size:11px;text-decoration:none;display:inline-flex}.Q9Ky_q_stageLink:hover,.Q9Ky_q_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_empty{opacity:.7;font-size:12px}.Q9Ky_q_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.Q9Ky_q_joined{border-color:var(--dsh-border,#2a2a30)}.Q9Ky_q_facts{text-align:left;min-width:0;color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;border-radius:6px;flex-direction:column;flex:1;gap:2px;padding:2px 4px;display:flex}.Q9Ky_q_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.Q9Ky_q_nameRow{align-items:center;gap:6px;display:flex}.Q9Ky_q_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.Q9Ky_q_stateDot.Q9Ky_q_live{background:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_stateDot.Q9Ky_q_live.Q9Ky_q_joined{background:var(--dsh-success,#3fb96f)}.Q9Ky_q_stateDot.Q9Ky_q_cold{background:var(--dsh-warning,#d9a13c)}.Q9Ky_q_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.Q9Ky_q_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.Q9Ky_q_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.Q9Ky_q_note{opacity:.6;font-size:11px}.Q9Ky_q_searchInput{box-sizing:border-box;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);width:100%;color:inherit;border-radius:6px;padding:5px 8px;font-size:12px}.Q9Ky_q_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.Q9Ky_q_sessionGroup{flex-direction:column;gap:2px;display:flex}.Q9Ky_q_groupHead{color:inherit;font:inherit;opacity:.75;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:4px 2px;font-size:11px;font-weight:600;display:flex}.Q9Ky_q_groupChevron{transition:transform .12s;display:inline-block}.Q9Ky_q_groupChevronClosed{transform:rotate(-90deg)}.Q9Ky_q_groupTag{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsh-border,#2a2a30);opacity:.8;border-radius:999px;flex:none;max-width:90px;padding:0 6px;font-size:10px;line-height:16px;overflow:hidden}.Q9Ky_q_groupButton{width:24px;height:24px;color:inherit;opacity:.55;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:none;font-size:12px;line-height:1}.Q9Ky_q_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.Q9Ky_q_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.Q9Ky_q_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.Q9Ky_q_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.Q9Ky_q_groupInput{border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);min-width:0;color:inherit;border-radius:6px;flex:1;padding:4px 8px;font-size:12px}.Q9Ky_q_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.Q9Ky_q_peerChip{border:1px solid var(--dsh-border,#2a2a30);border-radius:999px;align-items:center;gap:6px;padding:3px 8px;font-size:11px;display:inline-flex}.Q9Ky_q_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.Q9Ky_q_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.Q9Ky_q_activityList{flex-direction:column;gap:2px;display:flex}.Q9Ky_q_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.Q9Ky_q_activityRow[data-ok=false]{opacity:.55}.Q9Ky_q_activityDir{flex:none;font-weight:600}.Q9Ky_q_activityDir.Q9Ky_q_in{color:var(--dsh-success,#3fb96f)}.Q9Ky_q_activityDir.Q9Ky_q_out{color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.Q9Ky_q_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.Q9Ky_q_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.Q9Ky_q_activityRow[data-jump=true]{cursor:pointer}.Q9Ky_q_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.Q9Ky_q_fresh{animation:1.2s ease-out 3 Q9Ky_q_freshFlash}@keyframes Q9Ky_q_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_fresh{background:#4c8bf51f;animation:none}}.Q9Ky_q_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.Q9Ky_q_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.Q9Ky_q_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite Q9Ky_q_flightPulse}@keyframes Q9Ky_q_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_inFlightPulse{animation:none}}.Q9Ky_q_versionTag{color:var(--dsh-color-text-tertiary,#888);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;margin-left:auto;padding:1px 6px;font-size:10px;font-weight:500}.Q9Ky_q_inFlightStale{opacity:.55}.Q9Ky_q_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.Q9Ky_q_canvasCreate .Q9Ky_q_groupInput{flex:1;min-width:0}.Q9Ky_q_canvasChip{max-width:100%;color:var(--dsh-color-text-secondary,#aaa);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;align-items:center;gap:4px;margin:2px 4px 2px 0;padding:1px 6px;font-size:11px;display:inline-flex}.Q9Ky_q_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.Q9Ky_q_canvasChipRemove{cursor:pointer;min-width:24px;min-height:24px;color:var(--dsh-color-text-tertiary,#888);background:0 0;border:none;padding:2px 5px;font-size:11px;line-height:1}.Q9Ky_q_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.Q9Ky_q_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.Q9Ky_q_canvasChipRemove{transition:none}}.Q9Ky_q_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.Q9Ky_q_canvasActions{gap:6px;margin-top:4px;display:flex}.Q9Ky_q_inFlightStale .Q9Ky_q_inFlightPulse{opacity:.4;animation:none}.Q9Ky_q_failMark{color:#f87171;flex-shrink:0;font-size:11px}.Q9Ky_q_staleNote{color:var(--dsh-text-muted,#94a3b8b3);padding:4px 8px;font-size:11px}";
|
|
27
27
|
const tagId = "@nelsonlongxiang/dsh-open-a2a-net/A2aControl.module.css";
|
|
28
28
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
29
29
|
const tag = document.createElement("style");
|
|
@@ -33,67 +33,67 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
document.head.appendChild(tag);
|
|
34
34
|
}
|
|
35
35
|
var A2aControl_module_css_default = {
|
|
36
|
-
"activityDir": "
|
|
37
|
-
"activityList": "
|
|
38
|
-
"activityPeer": "
|
|
39
|
-
"activityRow": "
|
|
40
|
-
"activityTeam": "
|
|
41
|
-
"activityTime": "
|
|
42
|
-
"badgePulse": "
|
|
43
|
-
"canvasActions": "
|
|
44
|
-
"canvasChip": "
|
|
45
|
-
"canvasChipRemove": "
|
|
46
|
-
"canvasCreate": "
|
|
47
|
-
"cold": "
|
|
48
|
-
"description": "
|
|
49
|
-
"empty": "
|
|
50
|
-
"facts": "
|
|
51
|
-
"failMark": "
|
|
52
|
-
"flightPulse": "
|
|
53
|
-
"fresh": "
|
|
54
|
-
"freshFlash": "
|
|
55
|
-
"groupButton": "
|
|
56
|
-
"groupChevron": "
|
|
57
|
-
"groupChevronClosed": "
|
|
58
|
-
"groupHead": "
|
|
59
|
-
"groupInput": "
|
|
60
|
-
"groupNew": "
|
|
61
|
-
"groupOption": "
|
|
62
|
-
"groupOptionActive": "
|
|
63
|
-
"groupPicker": "
|
|
64
|
-
"groupTag": "
|
|
65
|
-
"in": "
|
|
66
|
-
"inFlightList": "
|
|
67
|
-
"inFlightPulse": "
|
|
68
|
-
"inFlightRow": "
|
|
69
|
-
"inFlightStale": "
|
|
70
|
-
"joined": "
|
|
71
|
-
"live": "
|
|
72
|
-
"name": "
|
|
73
|
-
"nameRow": "
|
|
74
|
-
"note": "
|
|
75
|
-
"out": "
|
|
76
|
-
"peerChip": "
|
|
77
|
-
"peerGrid": "
|
|
78
|
-
"peerHost": "
|
|
79
|
-
"peerScore": "
|
|
80
|
-
"popover": "
|
|
81
|
-
"row": "
|
|
82
|
-
"searchInput": "
|
|
83
|
-
"sectionTitle": "
|
|
84
|
-
"sessionGroup": "
|
|
85
|
-
"stageLink": "
|
|
86
|
-
"stageLinks": "
|
|
87
|
-
"stageRow": "
|
|
88
|
-
"stageTitle": "
|
|
89
|
-
"staleNote": "
|
|
90
|
-
"stateDot": "
|
|
91
|
-
"team": "
|
|
92
|
-
"title": "
|
|
93
|
-
"trigger": "
|
|
94
|
-
"unreadBadge": "
|
|
95
|
-
"versionTag": "
|
|
96
|
-
"wrap": "
|
|
36
|
+
"activityDir": "Q9Ky_q_activityDir",
|
|
37
|
+
"activityList": "Q9Ky_q_activityList",
|
|
38
|
+
"activityPeer": "Q9Ky_q_activityPeer",
|
|
39
|
+
"activityRow": "Q9Ky_q_activityRow",
|
|
40
|
+
"activityTeam": "Q9Ky_q_activityTeam",
|
|
41
|
+
"activityTime": "Q9Ky_q_activityTime",
|
|
42
|
+
"badgePulse": "Q9Ky_q_badgePulse",
|
|
43
|
+
"canvasActions": "Q9Ky_q_canvasActions",
|
|
44
|
+
"canvasChip": "Q9Ky_q_canvasChip",
|
|
45
|
+
"canvasChipRemove": "Q9Ky_q_canvasChipRemove",
|
|
46
|
+
"canvasCreate": "Q9Ky_q_canvasCreate",
|
|
47
|
+
"cold": "Q9Ky_q_cold",
|
|
48
|
+
"description": "Q9Ky_q_description",
|
|
49
|
+
"empty": "Q9Ky_q_empty",
|
|
50
|
+
"facts": "Q9Ky_q_facts",
|
|
51
|
+
"failMark": "Q9Ky_q_failMark",
|
|
52
|
+
"flightPulse": "Q9Ky_q_flightPulse",
|
|
53
|
+
"fresh": "Q9Ky_q_fresh",
|
|
54
|
+
"freshFlash": "Q9Ky_q_freshFlash",
|
|
55
|
+
"groupButton": "Q9Ky_q_groupButton",
|
|
56
|
+
"groupChevron": "Q9Ky_q_groupChevron",
|
|
57
|
+
"groupChevronClosed": "Q9Ky_q_groupChevronClosed",
|
|
58
|
+
"groupHead": "Q9Ky_q_groupHead",
|
|
59
|
+
"groupInput": "Q9Ky_q_groupInput",
|
|
60
|
+
"groupNew": "Q9Ky_q_groupNew",
|
|
61
|
+
"groupOption": "Q9Ky_q_groupOption",
|
|
62
|
+
"groupOptionActive": "Q9Ky_q_groupOptionActive",
|
|
63
|
+
"groupPicker": "Q9Ky_q_groupPicker",
|
|
64
|
+
"groupTag": "Q9Ky_q_groupTag",
|
|
65
|
+
"in": "Q9Ky_q_in",
|
|
66
|
+
"inFlightList": "Q9Ky_q_inFlightList",
|
|
67
|
+
"inFlightPulse": "Q9Ky_q_inFlightPulse",
|
|
68
|
+
"inFlightRow": "Q9Ky_q_inFlightRow",
|
|
69
|
+
"inFlightStale": "Q9Ky_q_inFlightStale",
|
|
70
|
+
"joined": "Q9Ky_q_joined",
|
|
71
|
+
"live": "Q9Ky_q_live",
|
|
72
|
+
"name": "Q9Ky_q_name",
|
|
73
|
+
"nameRow": "Q9Ky_q_nameRow",
|
|
74
|
+
"note": "Q9Ky_q_note",
|
|
75
|
+
"out": "Q9Ky_q_out",
|
|
76
|
+
"peerChip": "Q9Ky_q_peerChip",
|
|
77
|
+
"peerGrid": "Q9Ky_q_peerGrid",
|
|
78
|
+
"peerHost": "Q9Ky_q_peerHost",
|
|
79
|
+
"peerScore": "Q9Ky_q_peerScore",
|
|
80
|
+
"popover": "Q9Ky_q_popover",
|
|
81
|
+
"row": "Q9Ky_q_row",
|
|
82
|
+
"searchInput": "Q9Ky_q_searchInput",
|
|
83
|
+
"sectionTitle": "Q9Ky_q_sectionTitle",
|
|
84
|
+
"sessionGroup": "Q9Ky_q_sessionGroup",
|
|
85
|
+
"stageLink": "Q9Ky_q_stageLink",
|
|
86
|
+
"stageLinks": "Q9Ky_q_stageLinks",
|
|
87
|
+
"stageRow": "Q9Ky_q_stageRow",
|
|
88
|
+
"stageTitle": "Q9Ky_q_stageTitle",
|
|
89
|
+
"staleNote": "Q9Ky_q_staleNote",
|
|
90
|
+
"stateDot": "Q9Ky_q_stateDot",
|
|
91
|
+
"team": "Q9Ky_q_team",
|
|
92
|
+
"title": "Q9Ky_q_title",
|
|
93
|
+
"trigger": "Q9Ky_q_trigger",
|
|
94
|
+
"unreadBadge": "Q9Ky_q_unreadBadge",
|
|
95
|
+
"versionTag": "Q9Ky_q_versionTag",
|
|
96
|
+
"wrap": "Q9Ky_q_wrap"
|
|
97
97
|
};
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region lib/client/A2aControl.js
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The plugin row's configuration: the `Config` interface and its Schemastery schema (moved out of index.ts, #77). */
|
|
2
|
+
import s from '@deepseek-ai/schemastery';
|
|
3
|
+
import { TASK_STALE_TTL_MS } from "./task-ledger.js";
|
|
4
|
+
import { DEFAULT_TARGET_QUEUE_CAP } from "./queue-guard.js";
|
|
5
|
+
/** Schemastery configuration for the A2A client plugin row. */
|
|
6
|
+
export const Config = s.object({
|
|
7
|
+
apiKey: s.string().default(''),
|
|
8
|
+
session: s.string().default(''),
|
|
9
|
+
team: s.string().default('dsh'),
|
|
10
|
+
/** S4 fix ③: host-side (agent-less) routing calls — audit them (default) or refuse outright. */
|
|
11
|
+
gateAgentLessCalls: s.union(['audit', 'refuse']).default('audit'),
|
|
12
|
+
settlementAuth: s.union(['off', 'observe', 'enforce']).default('observe'),
|
|
13
|
+
announce: s.boolean().default(false),
|
|
14
|
+
isolated: s.boolean().default(false),
|
|
15
|
+
forwardDirect: s.boolean().default(true),
|
|
16
|
+
agentName: s.string().default('DeepSeek Harness A2A node'),
|
|
17
|
+
peers: s.array(s.string()).default([]),
|
|
18
|
+
delegates: s.array(s.object({ name: s.string(), url: s.string(), publicKey: s.string().default('') })).default([]),
|
|
19
|
+
dshHome: s.string().default(''),
|
|
20
|
+
sessionNodes: s.boolean().default(true),
|
|
21
|
+
wakeJoinedOnBoot: s.boolean().default(false),
|
|
22
|
+
wakePrewarmDelayMs: s.number().default(10_000),
|
|
23
|
+
wakePrewarmQuietMs: s.number().default(5_000),
|
|
24
|
+
wakeBootStaggerMs: s.number().default(3_000),
|
|
25
|
+
wakeReconcile: s.boolean().default(true),
|
|
26
|
+
wakeReconcileIntervalMs: s.number().default(60_000),
|
|
27
|
+
wakeReconcileBackoffBaseMs: s.number().default(5_000),
|
|
28
|
+
wakeReconcileMaxBackoffMs: s.number().default(600_000),
|
|
29
|
+
/**
|
|
30
|
+
* S4 (gh#11 N7): the joinable-team curation list. A model calling
|
|
31
|
+
* a2a_team_join may only declare membership in a team with an EXACT
|
|
32
|
+
* entry here — trailing-`*` patterns are readable but authorize nothing
|
|
33
|
+
* (no mint rights: a pattern pre-authorizing every future name under it
|
|
34
|
+
* would let a model mint teams nobody created). Default empty: the
|
|
35
|
+
* owner's no-self-join ruling, institutionalized as default-deny; a
|
|
36
|
+
* refused join carries guidance instead of failing silently.
|
|
37
|
+
*/
|
|
38
|
+
teamJoinAllowlist: s.array(s.string()).default([]),
|
|
39
|
+
seatNames: s.dict(s.any()).default({}),
|
|
40
|
+
/**
|
|
41
|
+
* S3 (phase-2 default, owner ruling 2026-09-01): route dispatch checks
|
|
42
|
+
* caller↔target shared team membership before addressing — a session
|
|
43
|
+
* with no declared team has no network (the unteamed-peer defect the
|
|
44
|
+
* owner reproduced on 4080 and locally). Fleet compatibility rides the
|
|
45
|
+
* roster capability flag (0.5.44 cards advertise roster:true; older
|
|
46
|
+
* peers read as legacy). Set false to restore the open outbound seam.
|
|
47
|
+
*/
|
|
48
|
+
teamScopeRouting: s.boolean().default(true),
|
|
49
|
+
/**
|
|
50
|
+
* S5 (default-on, owner directive 2026-10-20): the information-plane
|
|
51
|
+
* tools answer from the joined-team scope only — a2a_teams lists
|
|
52
|
+
* [this host] rows plus peer rows serving a joined team (note field
|
|
53
|
+
* explains the scope); a2a_status narrows peers / in-flight / activity
|
|
54
|
+
* to that scope (local traffic and idempotency aggregates stay);
|
|
55
|
+
* a2a_probe sweeps serving peers and refuses a single URL whose
|
|
56
|
+
* verified card cannot prove service. A host whose nodes declare no
|
|
57
|
+
* teams keeps host-internal facts only. Set false to restore
|
|
58
|
+
* fleet-wide disclosure.
|
|
59
|
+
*/
|
|
60
|
+
teamScopeInfo: s.boolean().default(true),
|
|
61
|
+
/**
|
|
62
|
+
* Peer boundary model — connection layer: an empty list keeps the
|
|
63
|
+
* historical open-referral posture; a non-empty list admits a referral
|
|
64
|
+
* only when its host matches an entry (exact host[:port], bare domain
|
|
65
|
+
* suffix, or full URL). Seeds from `peers` are owner-configured and
|
|
66
|
+
* unaffected.
|
|
67
|
+
*/
|
|
68
|
+
peerAllowlist: s.array(s.string()).default([]),
|
|
69
|
+
/**
|
|
70
|
+
* Peer boundary model — connection layer: when false, referral URLs are
|
|
71
|
+
* neither learned nor offered; the tracked set stays the configured seed
|
|
72
|
+
* set for the lifetime of the config that named them.
|
|
73
|
+
*/
|
|
74
|
+
referralLearning: s.boolean().default(true),
|
|
75
|
+
/**
|
|
76
|
+
* gh#8 (N9): team-name resolution rides the operator-established peer
|
|
77
|
+
* basis (config seeds + delegates) only; the referral-learned tail's
|
|
78
|
+
* self-published card claims do not bind team names. Set true to restore
|
|
79
|
+
* the legacy whole-store basis (fleet migration window).
|
|
80
|
+
*/
|
|
81
|
+
referralTeamResolution: s.boolean().default(false),
|
|
82
|
+
/**
|
|
83
|
+
* #19 D2 (ruling final, owner-level knob): how much of a session team's
|
|
84
|
+
* activity EXCERPT the card face publishes in sessionTeams descriptions.
|
|
85
|
+
* 'full' (default) keeps the verbatim tail excerpt; 'masked' truncates
|
|
86
|
+
* to a shape-only prefix; 'off' omits the description field entirely.
|
|
87
|
+
* Titles and team handles stay in every tier — the knob is the excerpt,
|
|
88
|
+
* not the listing.
|
|
89
|
+
*/
|
|
90
|
+
exposureExcerpt: s.union(['full', 'masked', 'off']).default('full'),
|
|
91
|
+
/**
|
|
92
|
+
* gh#9+gh#10 OriginClaim window: absent envelopes still walk N3 during
|
|
93
|
+
* the window (counted as legacyInbound); closing it rejects them like
|
|
94
|
+
* failed signatures. See docs/protocol/delivery-origin-auth.md §4.
|
|
95
|
+
*/
|
|
96
|
+
originClaimWindow: s.boolean().default(true),
|
|
97
|
+
/**
|
|
98
|
+
* OriginClaim unbound window: also sign/accept the legacy envelope that
|
|
99
|
+
* names no audience (counted as unboundInbound); closing it leaves the
|
|
100
|
+
* audience-bound form only. See docs/protocol/delivery-origin-auth.md §2.1.
|
|
101
|
+
*/
|
|
102
|
+
originClaimUnboundWindow: s.boolean().default(true),
|
|
103
|
+
/** Extra OriginClaim audience origins (port-mapped/proxied hosts). */
|
|
104
|
+
originClaimAudiences: s.array(s.string()).default([]),
|
|
105
|
+
/** OriginClaim callerUrl override for pocket-tunneled hosts; '' = auto. */
|
|
106
|
+
originClaimUrl: s.string().default(''),
|
|
107
|
+
/**
|
|
108
|
+
* v0.5.23 (async-stall): delay before a delivered-but-unconsumed async
|
|
109
|
+
* route re-wakes its target with a one-line nudge (defect
|
|
110
|
+
* t-mt6nd0sq-hxuhj6). 0 disables the nudge entirely.
|
|
111
|
+
*/
|
|
112
|
+
asyncNudgeDelayMs: s.number().default(120_000),
|
|
113
|
+
/** gh#224: at most this many receipt-owing tasks queue for one target; the next is refused (-32009). 0 disables. */
|
|
114
|
+
targetQueueCap: s.number().default(DEFAULT_TARGET_QUEUE_CAP),
|
|
115
|
+
stateColdRowsTtlMs: s.number().default(5_000),
|
|
116
|
+
cardCacheTtlMs: s.number().default(60_000),
|
|
117
|
+
cardCacheNegativeTtlMs: s.number().default(30_000),
|
|
118
|
+
remoteRowsTtlMs: s.number().default(15_000),
|
|
119
|
+
cardTtlMs: s.number().default(172_800_000),
|
|
120
|
+
flushTimeoutMs: s.number().default(300_000),
|
|
121
|
+
routeTimeoutMs: s.number().default(1_800_000),
|
|
122
|
+
taskStaleTtlMs: s.number().default(TASK_STALE_TTL_MS),
|
|
123
|
+
nativeTeamsInbound: s.boolean().default(false),
|
|
124
|
+
nativeRoundWaitMs: s.number().default(180_000),
|
|
125
|
+
});
|