@nelsonlongxiang/dsh-open-a2a-net 0.6.32 → 0.6.33
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 +7 -1
- package/README_zh.md +7 -1
- package/assets/nexusDist/assets/index-BACVZe0j.css +1 -0
- package/assets/nexusDist/assets/index-Bbxnpf2p.js +9 -0
- package/assets/nexusDist/index.html +2 -2
- package/cli/a2a.mjs +5 -2
- package/lib/canvas-store.js +6 -0
- package/lib/client.js +63 -63
- package/lib/faces/admin.js +43 -4
- package/lib/faces/wire.js +8 -6
- package/lib/index.js +11 -6
- package/lib/layout-store.js +3 -2
- package/lib/roster-op.js +104 -0
- package/lib/skill/references/cli.md +1 -1
- package/lib/skill/references/router.md +1 -1
- package/lib/team-store.js +49 -1
- package/lib/types/canvas-store.d.ts +4 -0
- package/lib/types/faces/admin.d.ts +1 -0
- package/lib/types/faces/wire.d.ts +1 -0
- package/lib/types/roster-op.d.ts +21 -0
- package/lib/types/team-store.d.ts +7 -0
- package/nexus-stage/src/canvas-ops.ts +2 -1
- package/nexus-stage/src/layout-doc.ts +6 -4
- package/nexus-stage/src/main.ts +27 -2
- package/nexus-stage/src/planning-frame-hue.ts +13 -0
- package/nexus-stage/src/planning-legend.ts +1 -0
- package/nexus-stage/src/planning-menus.ts +28 -12
- package/nexus-stage/src/planning-roster.ts +15 -0
- package/nexus-stage/src/planning-types.ts +1 -1
- package/nexus-stage/src/planning-view.ts +29 -27
- package/nexus-stage/src/planning.css +2 -0
- package/nexus-stage/src/workbench.ts +2 -0
- package/package.json +3 -2
- package/assets/nexusDist/assets/index-D5H8R8de.js +0 -9
- package/assets/nexusDist/assets/index-D9GYZyGZ.css +0 -1
package/cli/a2a.mjs
CHANGED
|
@@ -39,7 +39,7 @@ const INBOX_CURSOR = join(IDENTITY_DIR, 'inbox.cursor')
|
|
|
39
39
|
const DEFAULT_HOME = 'http://127.0.0.1:3080'
|
|
40
40
|
const DEFAULT_SKILL_ROOTS = [join(homedir(), '.agents', 'skills')]
|
|
41
41
|
|
|
42
|
-
const USAGE = `a2a-net CLI — aligned with the plugin's
|
|
42
|
+
const USAGE = `a2a-net CLI — aligned with the plugin's model tools ('a2a tools' prints the full map)
|
|
43
43
|
|
|
44
44
|
usage: a2a <command> [args] [--home URL] [--key <apiKey>] [--json]
|
|
45
45
|
|
|
@@ -270,10 +270,12 @@ const TOOL_CATALOG = [
|
|
|
270
270
|
['a2a_tasks', '账本三层对账:pending(欠回执)/dead(超期)/archived(已结算)'],
|
|
271
271
|
['a2a_probe', '探活整个 tracked fleet 或单 peer:可达性+往返延迟+失败原因'],
|
|
272
272
|
['a2a_exposure', '设本会话的网络可见档:linked(下限)/fleet(自愿上调)'],
|
|
273
|
+
['a2a_file_send', '把文件暂存在本宿主并授权给目标队,只路由一条引用消息(字节不进消息)'],
|
|
274
|
+
['a2a_file_fetch', '按引用从暂存宿主拉取文件,核对大小与 sha256 后落到 a2a-inbox/'],
|
|
273
275
|
]
|
|
274
276
|
|
|
275
277
|
function printAlignment() {
|
|
276
|
-
console.log('
|
|
278
|
+
console.log('插件工具面与 CLI 对齐:\n')
|
|
277
279
|
for (const [tool, desc] of TOOL_CATALOG) console.log(` ${tool.padEnd(16)} ${desc}`)
|
|
278
280
|
console.log('\nCLI 对齐映射:')
|
|
279
281
|
const map = [
|
|
@@ -284,6 +286,7 @@ function printAlignment() {
|
|
|
284
286
|
['a2a tasks', 'a2a_tasks', '账本三层(pending/dead/archived)'],
|
|
285
287
|
['a2a probe', 'a2a_probe', 'peer 可达性(评分/链接/验证,读暖态)'],
|
|
286
288
|
['a2a report', 'a2a_report', '离网报告(CLI 形态:投给宿主进程队信箱)'],
|
|
289
|
+
['a2a file send/fetch', 'a2a_file_send/fetch', '文件通道(CLI 只能从自己的 home 宿主拉取)'],
|
|
287
290
|
['(N/A)', 'a2a_exposure', '暴露档是会话面概念——CLI 无会话;户口即其可见性'],
|
|
288
291
|
['(拖卡入队)', 'a2a_team_join/leave', '入队是会话手势——等价物:属主把 CLI 成员卡拖进 canvas 队(画布手势=运营者链接)'],
|
|
289
292
|
['a2a skills', '(插件无)', '加载本机技能目录输出(CLI 专有)'],
|
package/lib/canvas-store.js
CHANGED
|
@@ -240,3 +240,9 @@ export class CanvasStore {
|
|
|
240
240
|
this.persist();
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
/** One policy predicate for local and signed-remote canvas creation. */
|
|
244
|
+
export function canvasNameReserved(raw, zone, allowlist, roster) {
|
|
245
|
+
const name = raw.trim();
|
|
246
|
+
const team = `${zone}/${name}`;
|
|
247
|
+
return name.startsWith('roster:') || allowlist.includes(team) || roster.membersOf(team).length > 0;
|
|
248
|
+
}
|
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\tmp\
|
|
26
|
-
const css = ".
|
|
25
|
+
//#region \0dsh-css:D:\workspace\dsh-open-a2a-net\tmp\release-0.6.33-485b362\src\src\client\A2aControl.module.css.mjs
|
|
26
|
+
const css = "._9ANQVq_wrap{max-width:100%;display:inline-flex;position:relative}._9ANQVq_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}._9ANQVq_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 _9ANQVq_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes _9ANQVq_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){._9ANQVq_unreadBadge{animation:none}}._9ANQVq_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}._9ANQVq_title{font-size:13px;font-weight:600}._9ANQVq_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}._9ANQVq_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}._9ANQVq_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}._9ANQVq_stageLinks{gap:6px;display:flex}._9ANQVq_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}._9ANQVq_stageLink:hover,._9ANQVq_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}._9ANQVq_empty{opacity:.7;font-size:12px}._9ANQVq_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}._9ANQVq_joined{border-color:var(--dsh-border,#2a2a30)}._9ANQVq_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}._9ANQVq_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}._9ANQVq_nameRow{align-items:center;gap:6px;display:flex}._9ANQVq_stateDot{border-radius:50%;flex:none;width:7px;height:7px}._9ANQVq_stateDot._9ANQVq_live{background:var(--dsh-accent,#4c8bf5)}._9ANQVq_stateDot._9ANQVq_live._9ANQVq_joined{background:var(--dsh-success,#3fb96f)}._9ANQVq_stateDot._9ANQVq_cold{background:var(--dsh-warning,#d9a13c)}._9ANQVq_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}._9ANQVq_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._9ANQVq_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}._9ANQVq_note{opacity:.6;font-size:11px}._9ANQVq_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}._9ANQVq_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}._9ANQVq_sessionGroup{flex-direction:column;gap:2px;display:flex}._9ANQVq_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}._9ANQVq_groupChevron{transition:transform .12s;display:inline-block}._9ANQVq_groupChevronClosed{transform:rotate(-90deg)}._9ANQVq_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}._9ANQVq_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}._9ANQVq_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}._9ANQVq_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}._9ANQVq_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}._9ANQVq_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}._9ANQVq_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}._9ANQVq_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}._9ANQVq_peerGrid{flex-wrap:wrap;gap:6px;display:flex}._9ANQVq_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}._9ANQVq_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}._9ANQVq_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}._9ANQVq_activityList{flex-direction:column;gap:2px;display:flex}._9ANQVq_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}._9ANQVq_activityRow[data-ok=false]{opacity:.55}._9ANQVq_activityDir{flex:none;font-weight:600}._9ANQVq_activityDir._9ANQVq_in{color:var(--dsh-success,#3fb96f)}._9ANQVq_activityDir._9ANQVq_out{color:var(--dsh-accent,#4c8bf5)}._9ANQVq_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}._9ANQVq_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}._9ANQVq_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}._9ANQVq_activityRow[data-jump=true]{cursor:pointer}._9ANQVq_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}._9ANQVq_fresh{animation:1.2s ease-out 3 _9ANQVq_freshFlash}@keyframes _9ANQVq_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){._9ANQVq_fresh{background:#4c8bf51f;animation:none}}._9ANQVq_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}._9ANQVq_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}._9ANQVq_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite _9ANQVq_flightPulse}@keyframes _9ANQVq_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){._9ANQVq_inFlightPulse{animation:none}}._9ANQVq_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}._9ANQVq_inFlightStale{opacity:.55}._9ANQVq_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}._9ANQVq_canvasCreate ._9ANQVq_groupInput{flex:1;min-width:0}._9ANQVq_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}._9ANQVq_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._9ANQVq_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}._9ANQVq_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}._9ANQVq_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){._9ANQVq_canvasChipRemove{transition:none}}._9ANQVq_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}._9ANQVq_canvasActions{gap:6px;margin-top:4px;display:flex}._9ANQVq_inFlightStale ._9ANQVq_inFlightPulse{opacity:.4;animation:none}._9ANQVq_failMark{color:#f87171;flex-shrink:0;font-size:11px}._9ANQVq_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": "_9ANQVq_activityDir",
|
|
37
|
+
"activityList": "_9ANQVq_activityList",
|
|
38
|
+
"activityPeer": "_9ANQVq_activityPeer",
|
|
39
|
+
"activityRow": "_9ANQVq_activityRow",
|
|
40
|
+
"activityTeam": "_9ANQVq_activityTeam",
|
|
41
|
+
"activityTime": "_9ANQVq_activityTime",
|
|
42
|
+
"badgePulse": "_9ANQVq_badgePulse",
|
|
43
|
+
"canvasActions": "_9ANQVq_canvasActions",
|
|
44
|
+
"canvasChip": "_9ANQVq_canvasChip",
|
|
45
|
+
"canvasChipRemove": "_9ANQVq_canvasChipRemove",
|
|
46
|
+
"canvasCreate": "_9ANQVq_canvasCreate",
|
|
47
|
+
"cold": "_9ANQVq_cold",
|
|
48
|
+
"description": "_9ANQVq_description",
|
|
49
|
+
"empty": "_9ANQVq_empty",
|
|
50
|
+
"facts": "_9ANQVq_facts",
|
|
51
|
+
"failMark": "_9ANQVq_failMark",
|
|
52
|
+
"flightPulse": "_9ANQVq_flightPulse",
|
|
53
|
+
"fresh": "_9ANQVq_fresh",
|
|
54
|
+
"freshFlash": "_9ANQVq_freshFlash",
|
|
55
|
+
"groupButton": "_9ANQVq_groupButton",
|
|
56
|
+
"groupChevron": "_9ANQVq_groupChevron",
|
|
57
|
+
"groupChevronClosed": "_9ANQVq_groupChevronClosed",
|
|
58
|
+
"groupHead": "_9ANQVq_groupHead",
|
|
59
|
+
"groupInput": "_9ANQVq_groupInput",
|
|
60
|
+
"groupNew": "_9ANQVq_groupNew",
|
|
61
|
+
"groupOption": "_9ANQVq_groupOption",
|
|
62
|
+
"groupOptionActive": "_9ANQVq_groupOptionActive",
|
|
63
|
+
"groupPicker": "_9ANQVq_groupPicker",
|
|
64
|
+
"groupTag": "_9ANQVq_groupTag",
|
|
65
|
+
"in": "_9ANQVq_in",
|
|
66
|
+
"inFlightList": "_9ANQVq_inFlightList",
|
|
67
|
+
"inFlightPulse": "_9ANQVq_inFlightPulse",
|
|
68
|
+
"inFlightRow": "_9ANQVq_inFlightRow",
|
|
69
|
+
"inFlightStale": "_9ANQVq_inFlightStale",
|
|
70
|
+
"joined": "_9ANQVq_joined",
|
|
71
|
+
"live": "_9ANQVq_live",
|
|
72
|
+
"name": "_9ANQVq_name",
|
|
73
|
+
"nameRow": "_9ANQVq_nameRow",
|
|
74
|
+
"note": "_9ANQVq_note",
|
|
75
|
+
"out": "_9ANQVq_out",
|
|
76
|
+
"peerChip": "_9ANQVq_peerChip",
|
|
77
|
+
"peerGrid": "_9ANQVq_peerGrid",
|
|
78
|
+
"peerHost": "_9ANQVq_peerHost",
|
|
79
|
+
"peerScore": "_9ANQVq_peerScore",
|
|
80
|
+
"popover": "_9ANQVq_popover",
|
|
81
|
+
"row": "_9ANQVq_row",
|
|
82
|
+
"searchInput": "_9ANQVq_searchInput",
|
|
83
|
+
"sectionTitle": "_9ANQVq_sectionTitle",
|
|
84
|
+
"sessionGroup": "_9ANQVq_sessionGroup",
|
|
85
|
+
"stageLink": "_9ANQVq_stageLink",
|
|
86
|
+
"stageLinks": "_9ANQVq_stageLinks",
|
|
87
|
+
"stageRow": "_9ANQVq_stageRow",
|
|
88
|
+
"stageTitle": "_9ANQVq_stageTitle",
|
|
89
|
+
"staleNote": "_9ANQVq_staleNote",
|
|
90
|
+
"stateDot": "_9ANQVq_stateDot",
|
|
91
|
+
"team": "_9ANQVq_team",
|
|
92
|
+
"title": "_9ANQVq_title",
|
|
93
|
+
"trigger": "_9ANQVq_trigger",
|
|
94
|
+
"unreadBadge": "_9ANQVq_unreadBadge",
|
|
95
|
+
"versionTag": "_9ANQVq_versionTag",
|
|
96
|
+
"wrap": "_9ANQVq_wrap"
|
|
97
97
|
};
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region lib/client/A2aControl.js
|
package/lib/faces/admin.js
CHANGED
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import { isRemoteMemberRef, validRemoteMemberRef } from "../canvas-store.js";
|
|
21
|
+
import { canvasNameReserved as reservedCanvasName, isRemoteMemberRef, validRemoteMemberRef } from "../canvas-store.js";
|
|
22
22
|
import { refreshPeerCard, registerCanvasNotify } from "./canvas-notify.js";
|
|
23
23
|
import { applyOwnerSettings } from "../owner-settings.js";
|
|
24
|
+
import { rosterOp } from "../roster-op.js";
|
|
24
25
|
import { resolveStageMount } from "../stage-mount.js";
|
|
25
26
|
import { ARCHIVE_CAP, archivedEndedBy } from "../task-ledger.js";
|
|
26
27
|
import { MAX_LAYOUT_BODY_BYTES } from "../transport-caps.js";
|
|
@@ -108,6 +109,7 @@ export function registerAdminFaces(webServer, d) {
|
|
|
108
109
|
}
|
|
109
110
|
const body = JSON.stringify({
|
|
110
111
|
nodes: true,
|
|
112
|
+
zone: config.team,
|
|
111
113
|
version: PLUGIN_VERSION,
|
|
112
114
|
// gh#193: a dark host says so — an empty remote list is not an outage.
|
|
113
115
|
...(config.isolated ? { isolated: true } : {}),
|
|
@@ -533,6 +535,39 @@ export function registerAdminFaces(webServer, d) {
|
|
|
533
535
|
});
|
|
534
536
|
}),
|
|
535
537
|
}), 'a2a: fleet-canvas-op relay route');
|
|
538
|
+
// A roster owner gesture writes the same teams.json declarations as a2a_team_join; it cannot admit remote or unjoined ids.
|
|
539
|
+
ctx.effect(() => webServer.register({
|
|
540
|
+
kind: 'exact', path: '/__dsh_a2a/roster-op',
|
|
541
|
+
handler: controlRoute((req, res) => {
|
|
542
|
+
const send = (payload) => {
|
|
543
|
+
const text = JSON.stringify(payload);
|
|
544
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(text) });
|
|
545
|
+
res.end(text);
|
|
546
|
+
};
|
|
547
|
+
if (req.method !== 'POST') {
|
|
548
|
+
send({ ok: false, error: 'POST required' });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
readJsonBody(req, res, body => {
|
|
552
|
+
try {
|
|
553
|
+
send(rosterOp({
|
|
554
|
+
zone: config.team, home, store: teamMemberships, joined: id => joinedSessions.has(id),
|
|
555
|
+
canvasExists: name => canvasStore.hasTeam(name) || canvasStore.hasTeam('roster:' + name),
|
|
556
|
+
allowlist: () => d.ownerAllowlist, setAllowlist: list => { d.ownerAllowlist = list; },
|
|
557
|
+
audit: (action, team, id, ok, reason) => {
|
|
558
|
+
teamAuditRing.push({ at: Date.now(), action, team, id, ok, reason, actor: 'human:owner' });
|
|
559
|
+
if (teamAuditRing.length > 32)
|
|
560
|
+
teamAuditRing.splice(0, teamAuditRing.length - 32);
|
|
561
|
+
},
|
|
562
|
+
}, body));
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
d.logger.warn('roster-op failed: %s', String(error));
|
|
566
|
+
send({ ok: false, error: 'roster operation unavailable' });
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
}),
|
|
570
|
+
}), 'a2a: owner roster route');
|
|
536
571
|
// Owner ruling 2026-10-29 (single owner + mirror): tell linked peers to re-read this host's card on any canvas change.
|
|
537
572
|
registerCanvasNotify(webServer, d);
|
|
538
573
|
// gh#34 owner three-key panel: the remote build-out unlock knobs,
|
|
@@ -648,6 +683,7 @@ export function registerAdminFaces(webServer, d) {
|
|
|
648
683
|
// remembered intent): canvas routing only reaches sessions the user
|
|
649
684
|
// put on the network by gesture — no routing backdoor over unjoined
|
|
650
685
|
// sessions.
|
|
686
|
+
const canvasNameReserved = (name) => reservedCanvasName(name, config.team, d.ownerAllowlist, teamMemberships);
|
|
651
687
|
ctx.effect(() => webServer.register({
|
|
652
688
|
kind: 'exact',
|
|
653
689
|
path: '/__dsh_a2a/canvas',
|
|
@@ -657,8 +693,8 @@ export function registerAdminFaces(webServer, d) {
|
|
|
657
693
|
let payload;
|
|
658
694
|
if (action === 'create') {
|
|
659
695
|
const name = typeof body.name === 'string' ? body.name : '';
|
|
660
|
-
const stored = canvasStore.create(name);
|
|
661
|
-
payload = JSON.stringify(stored === undefined ? { ok: false, error: 'invalid name or team cap reached' } : { ok: true, name: stored, teams: canvasStore.list() });
|
|
696
|
+
const stored = canvasNameReserved(name) ? undefined : canvasStore.create(name);
|
|
697
|
+
payload = JSON.stringify(stored === undefined ? { ok: false, error: 'invalid, roster-reserved name or team cap reached' } : { ok: true, name: stored, teams: canvasStore.list() });
|
|
662
698
|
}
|
|
663
699
|
else if (action === 'remove') {
|
|
664
700
|
const name = typeof body.name === 'string' ? body.name : '';
|
|
@@ -674,7 +710,10 @@ export function registerAdminFaces(webServer, d) {
|
|
|
674
710
|
const memberValid = isRemoteMemberRef(id)
|
|
675
711
|
? validRemoteMemberRef(id)
|
|
676
712
|
: id !== '' && (sessionNodes.has(id) || joinedSessions.has(id));
|
|
677
|
-
if (name
|
|
713
|
+
if (!canvasStore.hasTeam(name) && canvasNameReserved(name)) {
|
|
714
|
+
payload = JSON.stringify({ ok: false, error: 'roster reserves this canvas name' });
|
|
715
|
+
}
|
|
716
|
+
else if (name === '' || !memberValid) {
|
|
678
717
|
payload = JSON.stringify({ ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle like zone/a1b2c3d4) are required' });
|
|
679
718
|
}
|
|
680
719
|
else {
|
package/lib/faces/wire.js
CHANGED
|
@@ -74,7 +74,7 @@ export function settlerClaimFields(claim, originOf) {
|
|
|
74
74
|
* @param d - the captures listed on {@link WireDeps}.
|
|
75
75
|
*/
|
|
76
76
|
export function registerWireFaces(webServer, d) {
|
|
77
|
-
const { ctx, logger, config, cliDeposits, canvasStore, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, settleAndAnnounce, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
|
|
77
|
+
const { ctx, logger, config, cliDeposits, canvasStore, canvasNameReserved, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, settleAndAnnounce, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
|
|
78
78
|
// gh#45 CLI 户口卡面 (peer face, unsigned card serve): the deposited
|
|
79
79
|
// CLI's SIGNED card is served verbatim under its own base URL — an
|
|
80
80
|
// OriginClaim verification of a CLI envelope fetches exactly here
|
|
@@ -161,16 +161,18 @@ export function registerWireFaces(webServer, d) {
|
|
|
161
161
|
const id = (isRemoteMemberRef(rawId) ? localSessionOfHandle(rawId) : undefined) ?? rawId;
|
|
162
162
|
let payload;
|
|
163
163
|
if (op === 'create') {
|
|
164
|
-
const stored = canvasStore.create(name);
|
|
165
|
-
payload = stored === undefined ? { ok: false, error: 'invalid name or team cap reached' } : { ok: true, members: canvasStore.membersOf(stored) };
|
|
164
|
+
const stored = canvasNameReserved(name) ? undefined : canvasStore.create(name);
|
|
165
|
+
payload = stored === undefined ? { ok: false, error: 'invalid, roster-reserved name or team cap reached' } : { ok: true, members: canvasStore.membersOf(stored) };
|
|
166
166
|
}
|
|
167
167
|
else if (op === 'add-member') {
|
|
168
168
|
// gh#131: a handle is stored as sent, even one of this host's own — the
|
|
169
169
|
// local tiers read it as the session it names at route time.
|
|
170
170
|
const memberValid = isRemoteMemberRef(rawId) ? validRemoteMemberRef(rawId) : rawId !== '' && (sessionNodes.has(rawId) || joinedSessions.has(rawId));
|
|
171
|
-
payload = name
|
|
172
|
-
? { ok: false, error: '
|
|
173
|
-
:
|
|
171
|
+
payload = !canvasStore.hasTeam(name) && canvasNameReserved(name)
|
|
172
|
+
? { ok: false, error: 'roster reserves this canvas name' }
|
|
173
|
+
: name === '' || !memberValid
|
|
174
|
+
? { ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle) are required' }
|
|
175
|
+
: (canvasStore.create(name), { ok: canvasStore.addMember(name, rawId), members: canvasStore.membersOf(name) });
|
|
174
176
|
}
|
|
175
177
|
else if (op === 'remove-member') {
|
|
176
178
|
// Either shape may be stored (an older edit kept the handle): remove both (review of PR #151 P2-1).
|
package/lib/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export function resolveCallerLabel(input) {
|
|
|
28
28
|
}
|
|
29
29
|
import { signCard, verifyCard } from "./card.js";
|
|
30
30
|
import { GroupStore } from "./group-store.js";
|
|
31
|
-
import { CanvasStore, isRemoteMemberRef } from "./canvas-store.js";
|
|
31
|
+
import { CanvasStore, canvasNameReserved, isRemoteMemberRef } from "./canvas-store.js";
|
|
32
32
|
import { decodeSessionLog, extractTranscript, TRANSCRIPT_MAX_FILE_BYTES } from "./session-transcript.js";
|
|
33
33
|
import { LayoutStore } from "./layout-store.js";
|
|
34
34
|
import { JoinedSessions } from "./joined-store.js";
|
|
@@ -2043,7 +2043,10 @@ export function apply(ctx, config) {
|
|
|
2043
2043
|
// without a log.
|
|
2044
2044
|
void runReceiptLadder({
|
|
2045
2045
|
deliver: async (team) => {
|
|
2046
|
-
|
|
2046
|
+
// gh#217: across hosts the caller admits a session face it linked, never this host's process label; a
|
|
2047
|
+
// task for the bare process team keeps the label, the only host face the settlement judge accepts (N1).
|
|
2048
|
+
const peerCaller = row?.auth?.target.includes('/') === true ? sessionTeamOf(target) : undefined;
|
|
2049
|
+
const delivered = await dispatchAnywhere(team, receipt, session, undefined, new AbortController().signal, true, undefined, peerCaller);
|
|
2047
2050
|
// gh#141: the caller lane pays this delivery's inbound row across hosts (a
|
|
2048
2051
|
// same-host receipt already settled it in-process), unless the caller
|
|
2049
2052
|
// refused it. The owner tier is no payment here — but an owner mailbox
|
|
@@ -2531,6 +2534,7 @@ export function apply(ctx, config) {
|
|
|
2531
2534
|
config,
|
|
2532
2535
|
cliDeposits,
|
|
2533
2536
|
canvasStore,
|
|
2537
|
+
canvasNameReserved: name => canvasNameReserved(name, config.team, ownerAllowlist, teamMemberships),
|
|
2534
2538
|
sessionNodes,
|
|
2535
2539
|
joinedSessions,
|
|
2536
2540
|
idempotencyStore,
|
|
@@ -3250,6 +3254,7 @@ export function apply(ctx, config) {
|
|
|
3250
3254
|
if (!initiator) {
|
|
3251
3255
|
const callerTeams = teamMemberships.teamsOf(scopeCaller);
|
|
3252
3256
|
const team = String(args.team ?? '').trim();
|
|
3257
|
+
const sharedRoster = callerTeams.some(roster => teamMemberships.membersOf(roster).some(id => `${config.team}/${id8(id)}` === team));
|
|
3253
3258
|
// gh#71: the SHARED-CANVAS basis and the warm cache's peer-org
|
|
3254
3259
|
// basis (an org entry naming BOTH the caller and the target
|
|
3255
3260
|
// handle) count as declared here too — the same link the
|
|
@@ -3291,13 +3296,13 @@ export function apply(ctx, config) {
|
|
|
3291
3296
|
if (orgShared)
|
|
3292
3297
|
break;
|
|
3293
3298
|
}
|
|
3294
|
-
if (callerTeams.length === 0 && !sharedCanvas && !orgShared) {
|
|
3299
|
+
if (callerTeams.length === 0 && !sharedRoster && !sharedCanvas && !orgShared) {
|
|
3295
3300
|
return localRoute({
|
|
3296
3301
|
ok: false,
|
|
3297
3302
|
error: `team-scoped routing is on and this session (${id8(scopeCaller)}) has declared no team membership — a teamless node has no network. Declare one with a2a_team_join (subject to the owner's teamJoinAllowlist)`,
|
|
3298
3303
|
});
|
|
3299
3304
|
}
|
|
3300
|
-
if (!callerTeams.includes(team) && !sharedCanvas && !orgShared) {
|
|
3305
|
+
if (!callerTeams.includes(team) && !sharedRoster && !sharedCanvas && !orgShared) {
|
|
3301
3306
|
return localRoute({
|
|
3302
3307
|
ok: false,
|
|
3303
3308
|
error: `team-scoped routing is on: routing to "${team}" requires declared membership. This session's teams: ${callerTeams.join(', ')}. a2a_team_join can declare it (subject to the owner's teamJoinAllowlist)`,
|
|
@@ -3772,7 +3777,7 @@ export function apply(ctx, config) {
|
|
|
3772
3777
|
* receipt ladder can distinguish "delivered" from "no route" and its
|
|
3773
3778
|
* owner escalation actually fires for cross-node callback addresses.
|
|
3774
3779
|
*/
|
|
3775
|
-
async function dispatchAnywhere(team, message, callerSession, contextId, signal, asyncMode, taskId) {
|
|
3780
|
+
async function dispatchAnywhere(team, message, callerSession, contextId, signal, asyncMode, taskId, peerCaller) {
|
|
3776
3781
|
const local = await dispatchLocalCandidate(team, message, callerSession, contextId, signal, asyncMode, taskId);
|
|
3777
3782
|
// A background send books no outbound row: its birth facts have no row to go on.
|
|
3778
3783
|
if (taskId !== undefined)
|
|
@@ -3789,7 +3794,7 @@ export function apply(ctx, config) {
|
|
|
3789
3794
|
}
|
|
3790
3795
|
let lastError = 'no candidate delivered';
|
|
3791
3796
|
for (const candidate of candidates) {
|
|
3792
|
-
const result = await dispatchPeerCandidate(candidate, team, message, contextId, signal, callerSession, asyncMode, taskId);
|
|
3797
|
+
const result = await dispatchPeerCandidate(candidate, team, message, contextId, signal, peerCaller ?? callerSession, asyncMode, taskId);
|
|
3793
3798
|
if (result.ok)
|
|
3794
3799
|
return result;
|
|
3795
3800
|
lastError = result.error;
|
package/lib/layout-store.js
CHANGED
|
@@ -20,6 +20,8 @@ export const SCALE_MAX = 3;
|
|
|
20
20
|
/** Document shape caps: a layout taxonomy, not a database. */
|
|
21
21
|
export const LAYOUT_NODE_CAP = 256;
|
|
22
22
|
export const LAYOUT_FRAME_CAP = 96;
|
|
23
|
+
/** Canvas names stay ≤40; roster keys carry an extra seven-character namespace. */
|
|
24
|
+
const validFrame = (name) => name !== '' && !name.includes('/') && (name.startsWith('roster:') ? name.length > 7 && name.length <= 47 : name.length <= 40);
|
|
23
25
|
function clampCoord(v) {
|
|
24
26
|
const n = typeof v === 'number' ? v : Number.NaN;
|
|
25
27
|
if (!Number.isFinite(n))
|
|
@@ -79,8 +81,7 @@ function clampDoc(raw) {
|
|
|
79
81
|
const frames = {};
|
|
80
82
|
if (rec.frames !== null && typeof rec.frames === 'object') {
|
|
81
83
|
for (const [name, rect] of Object.entries(rec.frames)) {
|
|
82
|
-
|
|
83
|
-
if (typeof name !== 'string' || name === '' || name.includes('/') || name.length > 40)
|
|
84
|
+
if (!validFrame(name))
|
|
84
85
|
continue;
|
|
85
86
|
const r = clampRect(rect);
|
|
86
87
|
if (r !== undefined)
|
package/lib/roster-op.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Owner-controlled roster mutations from the planning board. */
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { applyOwnerSettings } from "./owner-settings.js";
|
|
5
|
+
import { TEAM_NAME_MAX } from "./team-store.js";
|
|
6
|
+
/** Writes complete snapshots; on a failed second write, rolls the first back. */
|
|
7
|
+
export function rosterOp(d, raw) {
|
|
8
|
+
const b = raw !== null && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
9
|
+
const op = b.op;
|
|
10
|
+
const name = b.name;
|
|
11
|
+
const team = typeof name === 'string' ? d.zone + '/' + name : '';
|
|
12
|
+
const action = op === 'remove-member' || op === 'dissolve' ? 'leave' : 'join';
|
|
13
|
+
const actor = 'human:owner';
|
|
14
|
+
const audit = (id, ok, why) => d.audit(action, team, id, ok, why);
|
|
15
|
+
const reject = (error) => { audit(typeof b.id === 'string' ? b.id : actor, false, error); return { ok: false, error }; };
|
|
16
|
+
if (typeof name !== 'string' || name !== name.trim() || name.length < 1 || name.length > 40 || name.includes('/') || name.includes('*') || name.startsWith('roster:') || /^\d+$/.test(name) || /[\r\n]/.test(name) || team.length > TEAM_NAME_MAX)
|
|
17
|
+
return reject('name must be one exact local roster name (no slash or canvas path)');
|
|
18
|
+
if (op === 'create' && d.canvasExists(name))
|
|
19
|
+
return reject('a canvas team already uses this name');
|
|
20
|
+
if (op !== 'create' && op !== 'add-member' && op !== 'remove-member' && op !== 'dissolve')
|
|
21
|
+
return reject('invalid roster operation');
|
|
22
|
+
const current = d.store.membersOf(team);
|
|
23
|
+
const allow = [...d.allowlist()];
|
|
24
|
+
const allowed = allow.includes(team);
|
|
25
|
+
if (op === 'add-member' && !allowed)
|
|
26
|
+
return reject('roster name is no longer in the exact allowlist');
|
|
27
|
+
if (op !== 'create' && !allowed && current.length === 0)
|
|
28
|
+
return reject('no such roster team');
|
|
29
|
+
if (op === 'create' && (!Array.isArray(b.ids) || b.ids.length < 2 || b.ids.some(x => typeof x !== 'string')))
|
|
30
|
+
return reject('create needs at least two joined local session ids');
|
|
31
|
+
if ((op === 'add-member' || op === 'remove-member') && (typeof b.id !== 'string' || b.id === ''))
|
|
32
|
+
return reject('local session id is required');
|
|
33
|
+
const add = op === 'create' ? b.ids : op === 'add-member' ? [b.id] : [];
|
|
34
|
+
if (new Set(add).size !== add.length || add.some(id => id === '' || !d.joined(id)))
|
|
35
|
+
return reject('only distinct joined local sessions can join a roster');
|
|
36
|
+
const members = op === 'create' || op === 'add-member' ? [...new Set([...current, ...add])] : op === 'remove-member' ? current.filter(id => id !== b.id) : [];
|
|
37
|
+
const nextAllow = op === 'create' && !allowed ? [...allow, team] : op === 'dissolve' ? allow.filter(x => x !== team) : allow;
|
|
38
|
+
const patch = join(d.home, 'cordis.patch.yml');
|
|
39
|
+
let before;
|
|
40
|
+
if ((op === 'create' && !allowed) || (op === 'dissolve' && allowed)) {
|
|
41
|
+
try {
|
|
42
|
+
before = existsSync(patch) ? readFileSync(patch, 'utf8') : undefined;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return reject('owner patch unavailable: ' + String(error));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const savePatch = (content) => {
|
|
49
|
+
mkdirSync(dirname(patch), { recursive: true });
|
|
50
|
+
const tmp = patch + '.' + String(process.pid) + '.tmp';
|
|
51
|
+
try {
|
|
52
|
+
writeFileSync(tmp, content, { mode: 0o600 });
|
|
53
|
+
renameSync(tmp, patch);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
try {
|
|
57
|
+
unlinkSync(tmp);
|
|
58
|
+
}
|
|
59
|
+
catch { /* original remains */ }
|
|
60
|
+
;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
let patchWritten = false;
|
|
65
|
+
try {
|
|
66
|
+
if (op === 'create' && !allowed) {
|
|
67
|
+
savePatch(applyOwnerSettings(before ?? '', { teamJoinAllowlist: nextAllow }).yaml);
|
|
68
|
+
patchWritten = true;
|
|
69
|
+
}
|
|
70
|
+
const done = d.store.replaceTeam(team, members);
|
|
71
|
+
if (!done.ok)
|
|
72
|
+
throw new Error(done.error);
|
|
73
|
+
if (op === 'dissolve' && allowed)
|
|
74
|
+
savePatch(applyOwnerSettings(before ?? '', { teamJoinAllowlist: nextAllow }).yaml);
|
|
75
|
+
d.setAllowlist(nextAllow);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
try {
|
|
79
|
+
if (patchWritten) {
|
|
80
|
+
if (before === undefined) {
|
|
81
|
+
if (existsSync(patch))
|
|
82
|
+
unlinkSync(patch);
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
savePatch(before);
|
|
86
|
+
}
|
|
87
|
+
if (op === 'dissolve') {
|
|
88
|
+
const undone = d.store.replaceTeam(team, current);
|
|
89
|
+
if (!undone.ok)
|
|
90
|
+
throw new Error(undone.error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (rollback) {
|
|
94
|
+
return reject(String(error) + '; rollback failed: ' + String(rollback));
|
|
95
|
+
}
|
|
96
|
+
return reject(String(error));
|
|
97
|
+
}
|
|
98
|
+
const delta = op === 'dissolve' ? current : op === 'remove-member' ? current.filter(id => !members.includes(id)) : members.filter(id => !current.includes(id));
|
|
99
|
+
if ((op === 'create' && !allowed) || (op === 'dissolve' && allowed))
|
|
100
|
+
audit(actor, true, op === 'create' ? 'created roster name' : 'dissolved roster name');
|
|
101
|
+
for (const id of delta)
|
|
102
|
+
audit(id, true, 'by human:owner');
|
|
103
|
+
return { ok: true, team, members };
|
|
104
|
+
}
|
|
@@ -5,7 +5,7 @@ agent harness — joins the network through the `a2a-net` command (bin of
|
|
|
5
5
|
`@nelsonlongxiang/dsh-open-a2a-net`, 0.6.27+). It has its own key, deposits
|
|
6
6
|
itself on ONE home host, and receives through a mailbox; nothing wakes.
|
|
7
7
|
`a2a-net help` prints the command map, `a2a-net tools` the alignment with the
|
|
8
|
-
|
|
8
|
+
model tools.
|
|
9
9
|
|
|
10
10
|
## Identity and home host
|
|
11
11
|
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
### 已知陷阱
|
|
88
88
|
- held 状态超过 72 小时就判为不通过:观察期里要及时 release 或撤回。
|
|
89
89
|
- a2a CLI 户口 4 小时过期,发送前先执行 `publish`。
|
|
90
|
-
- `verify-probe.mjs`
|
|
90
|
+
- `verify-probe.mjs` 每轮把提交树导出到一个新建的唯一目录 `<TEMP>/dsh-a2a-submission-XXXXXX`,并把路径写进 `verify/probe-summary.json`;`verify-recursive.mjs` 只认这个路径,不再读、也不再清空旧的固定目录 `dsh-a2a-submission`(#213)。这些导出目录不会自动删除,需要时手动清理自己的。
|
|
91
91
|
|
|
92
92
|
### 当前重点
|
|
93
93
|
- 跑通第一条真实的人类事件。需要三件事:
|
package/lib/team-store.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* fresh by the state route.
|
|
11
11
|
* @module @nelsonlongxiang/dsh-open-a2a-net/team-store
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { dirname } from 'node:path';
|
|
15
15
|
/** Upper bounds: a curation surface, not a database. */
|
|
16
16
|
export const TEAM_NODE_CAP = 64;
|
|
@@ -115,6 +115,54 @@ export class TeamMembershipStore {
|
|
|
115
115
|
this.notify();
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
/** One atomic roster replacement for the owner's workbench gesture. A failed disk write changes no in-memory membership. */
|
|
119
|
+
replaceTeam(team, members) {
|
|
120
|
+
const clean = validTeam(team);
|
|
121
|
+
if (clean === undefined || new Set(members).size !== members.length || members.some(id => id === ''))
|
|
122
|
+
return { ok: false, error: 'invalid roster team or member ids' };
|
|
123
|
+
if (this.membersOf(clean).join('\0') === members.join('\0'))
|
|
124
|
+
return { ok: true };
|
|
125
|
+
const wanted = new Set(members);
|
|
126
|
+
const next = this.entries.map(entry => ({ session: entry.session, teams: wanted.has(entry.session) ? [...entry.teams] : entry.teams.filter(t => t !== clean) }))
|
|
127
|
+
.filter(entry => entry.teams.length > 0);
|
|
128
|
+
for (const id of members) {
|
|
129
|
+
let entry = next.find(row => row.session === id);
|
|
130
|
+
if (entry === undefined) {
|
|
131
|
+
if (next.length >= TEAM_NODE_CAP)
|
|
132
|
+
return { ok: false, error: 'roster node capacity reached' };
|
|
133
|
+
entry = { session: id, teams: [] };
|
|
134
|
+
next.push(entry);
|
|
135
|
+
}
|
|
136
|
+
if (!entry.teams.includes(clean)) {
|
|
137
|
+
if (entry.teams.length >= TEAMS_PER_NODE_CAP)
|
|
138
|
+
return { ok: false, error: `session ${id} reached the team limit` };
|
|
139
|
+
entry.teams.push(clean);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
if (this.path !== '') {
|
|
144
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
145
|
+
const tmp = `${this.path}.${String(process.pid)}.tmp`;
|
|
146
|
+
try {
|
|
147
|
+
writeFileSync(tmp, JSON.stringify({ memberships: next }), { mode: 0o600 });
|
|
148
|
+
renameSync(tmp, this.path);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
try {
|
|
152
|
+
unlinkSync(tmp);
|
|
153
|
+
}
|
|
154
|
+
catch { /* the original snapshot stays */ }
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return { ok: false, error: `roster persistence failed: ${String(error)}` };
|
|
161
|
+
}
|
|
162
|
+
this.entries = next;
|
|
163
|
+
this.notify();
|
|
164
|
+
return { ok: true };
|
|
165
|
+
}
|
|
118
166
|
/** Drop every declaration of one session (a left or archived node). */
|
|
119
167
|
dropSession(session) {
|
|
120
168
|
const before = this.entries.length;
|