@infinisys/is-fabric-team 0.1.0 → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Infinity System contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @infinisys/is-fabric-team
2
2
 
3
+ > **会改动 DSH 的默认行为。** 它会关掉原生的 `tool-agent-team` 与 `ui-agent-team`,把 `subagent` 容量改成 16 个并发、深度 3,并注册四个 Agent 预设。后果与退回写法见 [docs/disclaimer.md](https://gitfox.studio/infinity/Fabric/blob/master/docs/disclaimer.md)。
4
+
3
5
  Infinity Fabric 的 **AI 团队插件**(v2):新增 **团队协作模式(Team Collaboration Mode)** 的四个 Agent 预设。会话根身份是 **assistant**;插件自动拉起 `lead` 和 8 个角色;章程/名册/身份是本地状态;GitFox 只跟踪工作项。工具面是**结构性**的:每个角色跑只挂载它该有能力的那套工具的预设,助手和 Lead 根本没有 shell / 写文件工具可"揽活"。
4
6
 
5
7
  ## 预设与工具面
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Spread the evidence gate's parsing across worker threads.
3
+ *
4
+ * One criterion's check is compared with every command line the ledger recorded,
5
+ * and a ledger has several criteria: the product is what saturates one core
6
+ * while every conversation waits behind it. The lines are independent, so they
7
+ * are handed out in batches and the answers are joined back in order.
8
+ *
9
+ * The callers are synchronous — they filter evidence while rendering a gate — so
10
+ * the pool keeps its promises synchronously: the main thread posts each batch,
11
+ * then waits on a shared counter until the workers have answered. That is the
12
+ * same shape the cost-meter ledger writer uses, and it keeps the gate code
13
+ * unchanged apart from the call it makes.
14
+ *
15
+ * @module is-fabric-team/lib/verdict-pool
16
+ */
17
+
18
+ import { Worker, MessageChannel, receiveMessageOnPort } from 'node:worker_threads';
19
+ import { availableParallelism } from 'node:os';
20
+
21
+ import { checkRunIn } from './check-run.js';
22
+
23
+ /** Below this many lines the hand-off costs more than it saves. */
24
+ const MIN_LINES = 64;
25
+
26
+ /** How long the caller waits before falling back to its own thread. */
27
+ const WAIT_MS = 10_000;
28
+
29
+ /** How many workers one host keeps. */
30
+ const POOL_SIZE = Math.max(1, Math.min(4, availableParallelism() - 1));
31
+
32
+ /** How long an idle pool keeps its threads. */
33
+ const IDLE_SHUTDOWN_MS = 60_000;
34
+
35
+ /** The live pool, created on first use. */
36
+ let pool = null;
37
+
38
+ /** Stops the pool once its threads have been idle for a minute. */
39
+ let idleTimer = null;
40
+
41
+ /**
42
+ * How one command line relates to the check, computed here.
43
+ *
44
+ * @param {string} command - recorded command line.
45
+ * @param {string} check - command text the criterion asks for.
46
+ * @returns {'attributable' | 'masked' | 'none'} verdict.
47
+ */
48
+ function verdictInline(command, check) {
49
+ const verdict = checkRunIn(command, check);
50
+ if (!verdict.invoked) return 'none';
51
+ return verdict.attributable ? 'attributable' : 'masked';
52
+ }
53
+
54
+ /**
55
+ * Start the pool.
56
+ *
57
+ * @returns {{ workers: Worker[], signal: Int32Array, pending: number }} state.
58
+ */
59
+ function startPool() {
60
+ const signal = new Int32Array(new SharedArrayBuffer(4));
61
+ const workers = [];
62
+ for (let index = 0; index < POOL_SIZE; index += 1) {
63
+ const worker = new Worker(new URL('./verdict-worker.mjs', import.meta.url), { execArgv: [] });
64
+ worker.unref();
65
+ workers.push(worker);
66
+ }
67
+ idleTimer ??= setInterval(() => stopPool(), IDLE_SHUTDOWN_MS);
68
+ idleTimer.unref?.();
69
+ return { workers, signal, pending: 0 };
70
+ }
71
+
72
+ /**
73
+ * Park this thread until every batch has answered.
74
+ *
75
+ * A parked thread runs no callbacks, so the answers are read straight off each
76
+ * port rather than awaited. The wait itself sleeps in short steps: the workers
77
+ * need the CPU this thread is holding.
78
+ *
79
+ * @param {Array<{ port1: MessagePort, verdicts: string[] | null }>} jobs - outstanding batches.
80
+ * @param {Int32Array} signal - shared word to sleep on.
81
+ * @param {number} until - wall-clock deadline.
82
+ * @returns {number} how many batches were still unanswered.
83
+ */
84
+ function drain(jobs, signal, until) {
85
+ let pending = jobs.length;
86
+ while (pending > 0 && Date.now() < until) {
87
+ for (const job of jobs) {
88
+ if (job.verdicts !== null) continue;
89
+ const received = receiveMessageOnPort(job.port1);
90
+ if (received === undefined) continue;
91
+ job.verdicts = received.message?.verdicts ?? [];
92
+ pending -= 1;
93
+ job.port1.close();
94
+ }
95
+ if (pending > 0) Atomics.wait(signal, 0, Atomics.load(signal, 0), 2);
96
+ }
97
+ return pending;
98
+ }
99
+
100
+ /**
101
+ * Ask the pool for the verdicts of one batch, synchronously.
102
+ *
103
+ * The caller cannot yield — it is filtering evidence mid-render — so the work is
104
+ * cut into slices, posted to the workers, and the answers joined back in order.
105
+ *
106
+ * @param {string[]} commands - command lines, in ledger order.
107
+ * @param {string} check - command text the criterion asks for.
108
+ * @returns {string[] | null} one verdict per line, or null when the pool could not answer.
109
+ */
110
+ function verdictsFromPool(commands, check) {
111
+ if (pool === null) pool = startPool();
112
+ const size = Math.min(pool.workers.length, Math.max(1, Math.ceil(commands.length / 8)));
113
+ const slices = Array.from({ length: size }, () => []);
114
+ commands.forEach((command, index) => {
115
+ slices[Math.floor((index / commands.length) * size)].push(command);
116
+ });
117
+ const jobs = slices
118
+ .filter((slice) => slice.length > 0)
119
+ .map((slice, slot) => {
120
+ const { port1, port2 } = new MessageChannel();
121
+ pool.workers[slot].postMessage({ id: slot, commands: slice, check, port: port2 }, [port2]);
122
+ return { slice, port1, verdicts: null };
123
+ });
124
+ return joinVerdicts(jobs, commands.length);
125
+ }
126
+
127
+ /**
128
+ * Wait for every batch and lay the answers out in the order the caller gave.
129
+ *
130
+ * @param {Array<{ slice: string[], port1: MessagePort, verdicts: string[] | null }>} jobs - batches.
131
+ * @param {number} total - how many lines were submitted.
132
+ * @returns {string[] | null} one verdict per line, or null on timeout.
133
+ */
134
+ function joinVerdicts(jobs, total) {
135
+ const unanswered = drain(jobs, pool.signal, Date.now() + WAIT_MS);
136
+ if (unanswered > 0) return null;
137
+ const verdicts = new Array(total);
138
+ let offset = 0;
139
+ for (const job of jobs) {
140
+ for (let position = 0; position < job.slice.length; position += 1) {
141
+ verdicts[offset + position] = job.verdicts[position] ?? 'none';
142
+ }
143
+ offset += job.slice.length;
144
+ }
145
+ return verdicts;
146
+ }
147
+
148
+ /**
149
+ * The verdicts of every line.
150
+ *
151
+ * @param {string[]} commands - command lines, in ledger order.
152
+ * @param {string} check - command text the criterion asks for.
153
+ * @returns {string[]} one verdict per line.
154
+ */
155
+ export function verdictsOf(commands, check) {
156
+ if (commands.length === 0) return [];
157
+ if (commands.length < MIN_LINES) return commands.map((command) => verdictInline(command, check));
158
+ return verdictsFromPool(commands, check) ?? commands.map((command) => verdictInline(command, check));
159
+ }
160
+
161
+ /**
162
+ * Drop the pool's threads; the next call starts them again.
163
+ *
164
+ * @returns {void}
165
+ */
166
+ export function stopPool() {
167
+ if (idleTimer !== null) {
168
+ clearInterval(idleTimer);
169
+ idleTimer = null;
170
+ }
171
+ if (pool === null) return;
172
+ for (const worker of pool.workers) void worker.terminate();
173
+ pool = null;
174
+ }
175
+
176
+ // The idle timer is started with the pool and stopped with it, so a hot reload
177
+ // that disposes this plugin leaves no interval behind counting down to nothing.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Worker side of the evidence gate: decide how a batch of recorded commands
3
+ * relates to one criterion's check.
4
+ *
5
+ * The main thread splits the ledger's command rows across workers and waits for
6
+ * the answers, so the gate no longer turns one core into the only core. Nothing
7
+ * here touches the filesystem or the ledger: it receives text, parses it, and
8
+ * answers with one verdict per line.
9
+ *
10
+ * @module is-fabric-team/lib/verdict-worker
11
+ */
12
+
13
+ import { parentPort } from 'node:worker_threads';
14
+
15
+ import { checkRunIn } from './check-run.js';
16
+
17
+ /**
18
+ * How one command line relates to the check.
19
+ *
20
+ * @param {string} command - recorded command line.
21
+ * @param {string} check - command text the criterion asks for.
22
+ * @returns {'attributable' | 'masked' | 'none'} verdict.
23
+ */
24
+ function verdictOf(command, check) {
25
+ const verdict = checkRunIn(command, check);
26
+ if (!verdict.invoked) return 'none';
27
+ return verdict.attributable ? 'attributable' : 'masked';
28
+ }
29
+
30
+ parentPort?.on('message', ({ id, commands, check, port }) => {
31
+ const verdicts = (commands ?? []).map((command) => verdictOf(command, check));
32
+ // Answer on the port this batch arrived with when there is one: the caller
33
+ // parks its thread and polls that port, so `parentPort` would never be read.
34
+ (port ?? parentPort)?.postMessage({ id, verdicts });
35
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infinisys/is-fabric-team",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Infinity Fabric AI Team: team collaboration mode, local roster and identities, unanimous voting, GitFox tracking and per-role hard boundaries.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,6 +28,7 @@
28
28
  "client.js",
29
29
  "mode.js",
30
30
  "lib/*.js",
31
+ "lib/*.mjs",
31
32
  "cordis.patch.yml",
32
33
  "locale/*.json",
33
34
  "icon.svg"
@@ -50,4 +51,4 @@
50
51
  ]
51
52
  }
52
53
  }
53
- }
54
+ }