@xemahq/opencode-xema-plugin 0.1.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -5
- package/dist/.source-stamp.json +2 -2
- package/dist/context.d.ts +0 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +0 -1
- package/dist/context.js.map +1 -1
- package/dist/hooks/subagent-fanout-ceiling.d.ts +39 -0
- package/dist/hooks/subagent-fanout-ceiling.d.ts.map +1 -0
- package/dist/hooks/subagent-fanout-ceiling.js +188 -0
- package/dist/hooks/subagent-fanout-ceiling.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -14
- package/dist/index.js.map +1 -1
- package/dist/opencode-types.d.ts +14 -20
- package/dist/opencode-types.d.ts.map +1 -1
- package/dist/opencode-types.js +1 -3
- package/dist/opencode-types.js.map +1 -1
- package/package.json +4 -5
- package/plugin.mjs +7463 -2324
- package/dist/session-state.d.ts +0 -20
- package/dist/session-state.d.ts.map +0 -1
- package/dist/session-state.js +0 -43
- package/dist/session-state.js.map +0 -1
- package/dist/tools/runtime.d.ts +0 -2
- package/dist/tools/runtime.d.ts.map +0 -1
- package/dist/tools/runtime.js +0 -2
- package/dist/tools/runtime.js.map +0 -1
- package/dist/tools/service-token.d.ts +0 -3
- package/dist/tools/service-token.d.ts.map +0 -1
- package/dist/tools/service-token.js +0 -31
- package/dist/tools/service-token.js.map +0 -1
- package/dist/tools/shared.d.ts +0 -3
- package/dist/tools/shared.d.ts.map +0 -1
- package/dist/tools/shared.js +0 -11
- package/dist/tools/shared.js.map +0 -1
- package/dist/tools/xema-memory.d.ts +0 -31
- package/dist/tools/xema-memory.d.ts.map +0 -1
- package/dist/tools/xema-memory.js +0 -157
- package/dist/tools/xema-memory.js.map +0 -1
package/README.md
CHANGED
|
@@ -28,16 +28,44 @@
|
|
|
28
28
|
|
|
29
29
|
## Overview
|
|
30
30
|
|
|
31
|
-
An Opencode plugin that registers Xema-namespaced runtime tools
|
|
32
|
-
system-prompt overlay hook
|
|
33
|
-
registers its tools unconditionally and
|
|
34
|
-
tool-execution time, where each tool reads the
|
|
35
|
-
worker filesystem and rejects calls the invoking
|
|
31
|
+
An Opencode plugin that registers Xema-namespaced runtime tools, a dynamic
|
|
32
|
+
system-prompt overlay hook, and the sub-agent fan-out ceiling. The plugin is
|
|
33
|
+
evaluated once at agent startup; it registers its tools unconditionally and
|
|
34
|
+
defers per-role authorization to tool-execution time, where each tool reads the
|
|
35
|
+
invocation context from the worker filesystem and rejects calls the invoking
|
|
36
|
+
role is not permitted to make.
|
|
36
37
|
|
|
37
38
|
## When to use it
|
|
38
39
|
|
|
39
40
|
- Use it to add the Xema runtime tools and prompt overlay to an Opencode agent
|
|
40
41
|
runtime.
|
|
42
|
+
- Use it to bound how many sub-agents one worker may run at once.
|
|
43
|
+
|
|
44
|
+
## The sub-agent fan-out ceiling
|
|
45
|
+
|
|
46
|
+
Opencode bounds how DEEP delegation may nest (`subagent_depth`). Nothing bounds
|
|
47
|
+
how WIDE a turn may go, so at the platform's declared depth an unbounded
|
|
48
|
+
branching factor is an unbounded process — every `task` call is forked with no
|
|
49
|
+
pool, no semaphore and no queue.
|
|
50
|
+
|
|
51
|
+
This plugin adds the missing pool cap. It is BACKPRESSURE, not a refusal: when
|
|
52
|
+
the worker is already running its ceiling of sub-agents, the next `task` call
|
|
53
|
+
WAITS for a slot instead of being denied. An agent that cannot spawn a helper
|
|
54
|
+
would produce a wrong answer; one that waits produces a slower right one.
|
|
55
|
+
|
|
56
|
+
Configuration exists to TIGHTEN, never to enable:
|
|
57
|
+
|
|
58
|
+
| | |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Default | 8 concurrent `task` calls per worker |
|
|
61
|
+
| Tighten with | `XEMA_SUBAGENT_FANOUT_LIMIT` (1..8) |
|
|
62
|
+
| Unset or empty | the platform default — never zero, never "off" |
|
|
63
|
+
| Above the default | refused at boot, with the reason |
|
|
64
|
+
|
|
65
|
+
Every degradation is logged: a task that waits says so and for how long, and
|
|
66
|
+
both leak backstops (a `session.idle` sweep and a per-permit TTL) name what they
|
|
67
|
+
reclaimed. A ceiling that silently stops binding is indistinguishable from
|
|
68
|
+
working software until somebody asks.
|
|
41
69
|
|
|
42
70
|
## Installation
|
|
43
71
|
|
package/dist/.source-stamp.json
CHANGED
package/dist/context.d.ts
CHANGED
|
@@ -39,7 +39,6 @@ declare const ContextJsonSchema: z.ZodObject<{
|
|
|
39
39
|
"own-pages-only": "own-pages-only";
|
|
40
40
|
"full-write": "full-write";
|
|
41
41
|
}>;
|
|
42
|
-
allowedXemaTools: z.ZodArray<z.ZodString>;
|
|
43
42
|
}, z.core.$strip>;
|
|
44
43
|
git: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
45
44
|
autoCommit: z.ZodEnum<{
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgGrB,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAUrE,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,GACnB,iBAAiB,GAAG,IAAI,CAQ1B;AAUD,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,iBAAiB,GAAG,IAAI,CAiB1B"}
|
package/dist/context.js
CHANGED
|
@@ -37,7 +37,6 @@ const ContextJsonSchema = z.object({
|
|
|
37
37
|
mayWriteKnowledgeBase: z.boolean().default(true),
|
|
38
38
|
mayEditRepos: z.boolean().default(false),
|
|
39
39
|
writePolicy: z.enum(['read-only', 'own-pages-only', 'full-write']),
|
|
40
|
-
allowedXemaTools: z.array(z.string()).min(1),
|
|
41
40
|
}),
|
|
42
41
|
git: z
|
|
43
42
|
.object({
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAC3C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;YACX,aAAa;YACb,aAAa;YACb,eAAe;YACf,2BAA2B;YAC3B,iBAAiB;YACjB,eAAe;YACf,uBAAuB;YACvB,UAAU;YACV,eAAe;YACf,SAAS;YAKT,UAAU;SACX,CAAC;QACF,OAAO,EAAE,CAAC;aACP,IAAI,CAAC,CAAC,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,aAAa,CAAC,CAAC;aAC/E,QAAQ,EAAE;KACd,CAAC;IACF,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;QAK/B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;KACrC,CAAC;SACD,OAAO,EAAE;IACZ,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,yBAAyB,CAAC,CAAC;QAC3E,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,qBAAqB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QAChD,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QACxC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const TASK_TOOL_ID = "task";
|
|
2
|
+
export declare const DEFAULT_SUBAGENT_FANOUT_LIMIT = 8;
|
|
3
|
+
export declare const DEFAULT_FANOUT_WAIT_TIMEOUT_MS: number;
|
|
4
|
+
export declare const DEFAULT_PERMIT_TTL_MS: number;
|
|
5
|
+
export declare const FANOUT_LIMIT_ENV = "XEMA_SUBAGENT_FANOUT_LIMIT";
|
|
6
|
+
export declare function resolveSubagentFanoutLimit(env?: Readonly<Record<string, string | undefined>>): number;
|
|
7
|
+
export interface FanoutCeilingStats {
|
|
8
|
+
readonly limit: number;
|
|
9
|
+
readonly inFlight: number;
|
|
10
|
+
readonly waiting: number;
|
|
11
|
+
readonly waitedCount: number;
|
|
12
|
+
readonly timedOutCount: number;
|
|
13
|
+
readonly sweptCount: number;
|
|
14
|
+
readonly ttlReclaimedCount: number;
|
|
15
|
+
readonly armedTimers: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SubagentFanoutCeiling {
|
|
18
|
+
readonly before: (input: {
|
|
19
|
+
tool: string;
|
|
20
|
+
sessionID: string;
|
|
21
|
+
callID: string;
|
|
22
|
+
}) => Promise<void>;
|
|
23
|
+
readonly after: (input: {
|
|
24
|
+
tool: string;
|
|
25
|
+
sessionID: string;
|
|
26
|
+
callID: string;
|
|
27
|
+
}) => void;
|
|
28
|
+
readonly onSessionIdle: (sessionId: string) => void;
|
|
29
|
+
readonly dispose: () => void;
|
|
30
|
+
readonly stats: () => FanoutCeilingStats;
|
|
31
|
+
}
|
|
32
|
+
export interface FanoutCeilingOptions {
|
|
33
|
+
readonly limit: number;
|
|
34
|
+
readonly waitTimeoutMs?: number;
|
|
35
|
+
readonly permitTtlMs?: number;
|
|
36
|
+
readonly now?: () => number;
|
|
37
|
+
}
|
|
38
|
+
export declare function createSubagentFanoutCeiling(options: FanoutCeilingOptions): SubagentFanoutCeiling;
|
|
39
|
+
//# sourceMappingURL=subagent-fanout-ceiling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-fanout-ceiling.d.ts","sourceRoot":"","sources":["../../src/hooks/subagent-fanout-ceiling.ts"],"names":[],"mappings":"AA0FA,eAAO,MAAM,YAAY,SAAS,CAAC;AA6CnC,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAW/C,eAAO,MAAM,8BAA8B,QAAiB,CAAC;AAU7D,eAAO,MAAM,qBAAqB,QAAiB,CAAC;AAGpD,eAAO,MAAM,gBAAgB,+BAA+B,CAAC;AAyB7D,wBAAgB,0BAA0B,CACxC,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAC9D,MAAM,CA8BR;AAoBD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IAiBnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IAEpC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpB,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IAEX,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAgBpD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,kBAAkB,CAAC;CAC1C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAOD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,oBAAoB,GAC5B,qBAAqB,CAgPvB"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { info, warn, debug } from '../logger.js';
|
|
2
|
+
export const TASK_TOOL_ID = 'task';
|
|
3
|
+
export const DEFAULT_SUBAGENT_FANOUT_LIMIT = 8;
|
|
4
|
+
export const DEFAULT_FANOUT_WAIT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
5
|
+
export const DEFAULT_PERMIT_TTL_MS = 30 * 60 * 1000;
|
|
6
|
+
export const FANOUT_LIMIT_ENV = 'XEMA_SUBAGENT_FANOUT_LIMIT';
|
|
7
|
+
export function resolveSubagentFanoutLimit(env = process.env) {
|
|
8
|
+
const declared = env[FANOUT_LIMIT_ENV];
|
|
9
|
+
if (declared === undefined || declared.trim() === '') {
|
|
10
|
+
return DEFAULT_SUBAGENT_FANOUT_LIMIT;
|
|
11
|
+
}
|
|
12
|
+
const parsed = Number(declared.trim());
|
|
13
|
+
if (!Number.isInteger(parsed)) {
|
|
14
|
+
throw new Error(`${FANOUT_LIMIT_ENV}="${declared}" is not an integer. Expected a whole number of ` +
|
|
15
|
+
`concurrently executing sub-agent tasks between 1 and ${DEFAULT_SUBAGENT_FANOUT_LIMIT}.`);
|
|
16
|
+
}
|
|
17
|
+
if (parsed < 1) {
|
|
18
|
+
throw new Error(`${FANOUT_LIMIT_ENV}="${declared}" is below 1. A ceiling of 0 would park EVERY delegation ` +
|
|
19
|
+
`until it timed out, turning a resource bound into an outage. Remove the variable to ` +
|
|
20
|
+
`accept the platform default (${DEFAULT_SUBAGENT_FANOUT_LIMIT}).`);
|
|
21
|
+
}
|
|
22
|
+
if (parsed > DEFAULT_SUBAGENT_FANOUT_LIMIT) {
|
|
23
|
+
throw new Error(`${FANOUT_LIMIT_ENV}="${declared}" is above the platform default ` +
|
|
24
|
+
`(${DEFAULT_SUBAGENT_FANOUT_LIMIT}). Configuration may only TIGHTEN this ceiling. The ` +
|
|
25
|
+
`default is a stated platform decision; a wider one moves ` +
|
|
26
|
+
`DEFAULT_SUBAGENT_FANOUT_LIMIT, where the grounds are recorded.`);
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
export function createSubagentFanoutCeiling(options) {
|
|
31
|
+
const limit = options.limit;
|
|
32
|
+
const waitTimeoutMs = options.waitTimeoutMs ?? DEFAULT_FANOUT_WAIT_TIMEOUT_MS;
|
|
33
|
+
const permitTtlMs = options.permitTtlMs ?? DEFAULT_PERMIT_TTL_MS;
|
|
34
|
+
const now = options.now ?? (() => Date.now());
|
|
35
|
+
const held = new Map();
|
|
36
|
+
const waiters = [];
|
|
37
|
+
let waitedCount = 0;
|
|
38
|
+
let timedOutCount = 0;
|
|
39
|
+
let sweptCount = 0;
|
|
40
|
+
let ttlReclaimedCount = 0;
|
|
41
|
+
const armed = new Set();
|
|
42
|
+
function arm(fn, ms, keepProcessAlive) {
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
armed.delete(timer);
|
|
45
|
+
fn();
|
|
46
|
+
}, ms);
|
|
47
|
+
if (!keepProcessAlive)
|
|
48
|
+
timer.unref?.();
|
|
49
|
+
armed.add(timer);
|
|
50
|
+
return timer;
|
|
51
|
+
}
|
|
52
|
+
function disarm(timer) {
|
|
53
|
+
if (timer === undefined)
|
|
54
|
+
return;
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
armed.delete(timer);
|
|
57
|
+
}
|
|
58
|
+
function take(callId, sessionId) {
|
|
59
|
+
const ttlTimer = arm(() => {
|
|
60
|
+
if (!held.has(callId))
|
|
61
|
+
return;
|
|
62
|
+
ttlReclaimedCount += 1;
|
|
63
|
+
warn(`fanout-ceiling: permit TTL expired for call=${callId} session=${sessionId} after ` +
|
|
64
|
+
`${permitTtlMs}ms — force-releasing. A task held a slot longer than any healthy ` +
|
|
65
|
+
`sub-agent turn; the pool would otherwise have shrunk permanently.`);
|
|
66
|
+
release(callId, 'ttl');
|
|
67
|
+
}, permitTtlMs, false);
|
|
68
|
+
held.set(callId, { sessionId, acquiredAt: now(), ttlTimer });
|
|
69
|
+
}
|
|
70
|
+
function release(callId, reason) {
|
|
71
|
+
const permit = held.get(callId);
|
|
72
|
+
if (permit === undefined)
|
|
73
|
+
return;
|
|
74
|
+
disarm(permit.ttlTimer);
|
|
75
|
+
held.delete(callId);
|
|
76
|
+
if (reason !== 'completed') {
|
|
77
|
+
debug(`fanout-ceiling: reclaimed call=${callId} via ${reason} ` +
|
|
78
|
+
`(held ${now() - permit.acquiredAt}ms)`);
|
|
79
|
+
}
|
|
80
|
+
drain();
|
|
81
|
+
}
|
|
82
|
+
function drain() {
|
|
83
|
+
while (waiters.length > 0 && held.size < limit) {
|
|
84
|
+
const next = waiters.shift();
|
|
85
|
+
if (next === undefined)
|
|
86
|
+
return;
|
|
87
|
+
disarm(next.timer);
|
|
88
|
+
const waitedMs = now() - next.since;
|
|
89
|
+
waitedCount += 1;
|
|
90
|
+
take(next.callId, next.sessionId);
|
|
91
|
+
info(`fanout-ceiling: admitted call=${next.callId} session=${next.sessionId} after waiting ` +
|
|
92
|
+
`${waitedMs}ms (limit=${limit}, in-flight=${held.size}, still waiting=${waiters.length})`);
|
|
93
|
+
next.grant();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function before(input) {
|
|
97
|
+
if (input.tool !== TASK_TOOL_ID)
|
|
98
|
+
return;
|
|
99
|
+
const { callID: callId, sessionID: sessionId } = input;
|
|
100
|
+
if (held.size < limit) {
|
|
101
|
+
take(callId, sessionId);
|
|
102
|
+
debug(`fanout-ceiling: admitted call=${callId} immediately ` +
|
|
103
|
+
`(in-flight=${held.size}/${limit})`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const since = now();
|
|
107
|
+
warn(`fanout-ceiling: sub-agent fan-out at ceiling — call=${callId} session=${sessionId} is ` +
|
|
108
|
+
`WAITING (limit=${limit}, in-flight=${held.size}, already waiting=${waiters.length}). ` +
|
|
109
|
+
`This is backpressure, not a refusal: the task will start when a slot frees.`);
|
|
110
|
+
await new Promise((resolve) => {
|
|
111
|
+
const waiter = {
|
|
112
|
+
callId,
|
|
113
|
+
sessionId,
|
|
114
|
+
since,
|
|
115
|
+
grant: resolve,
|
|
116
|
+
timer: undefined,
|
|
117
|
+
};
|
|
118
|
+
waiter.timer = arm(() => {
|
|
119
|
+
const idx = waiters.indexOf(waiter);
|
|
120
|
+
if (idx === -1)
|
|
121
|
+
return;
|
|
122
|
+
waiters.splice(idx, 1);
|
|
123
|
+
timedOutCount += 1;
|
|
124
|
+
warn(`fanout-ceiling: call=${callId} session=${sessionId} waited ${waitTimeoutMs}ms and is ` +
|
|
125
|
+
`being ADMITTED OVER the ceiling (limit=${limit}, in-flight=${held.size}). The pool ` +
|
|
126
|
+
`did not free a slot in that window — suspect a leaked permit or a stuck sub-agent.`);
|
|
127
|
+
take(callId, sessionId);
|
|
128
|
+
resolve();
|
|
129
|
+
}, waitTimeoutMs, true);
|
|
130
|
+
waiters.push(waiter);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function after(input) {
|
|
134
|
+
if (input.tool !== TASK_TOOL_ID)
|
|
135
|
+
return;
|
|
136
|
+
release(input.callID, 'completed');
|
|
137
|
+
}
|
|
138
|
+
function onSessionIdle(sessionId) {
|
|
139
|
+
const stale = [];
|
|
140
|
+
for (const [callId, permit] of held) {
|
|
141
|
+
if (permit.sessionId === sessionId)
|
|
142
|
+
stale.push(callId);
|
|
143
|
+
}
|
|
144
|
+
if (stale.length === 0)
|
|
145
|
+
return;
|
|
146
|
+
sweptCount += stale.length;
|
|
147
|
+
warn(`fanout-ceiling: session=${sessionId} went idle still holding ${stale.length} permit(s) ` +
|
|
148
|
+
`[${stale.join(', ')}] — reclaiming. These tasks ended without a tool.execute.after, ` +
|
|
149
|
+
`which upstream does not fire on the failure path.`);
|
|
150
|
+
for (const callId of stale)
|
|
151
|
+
release(callId, 'session-idle');
|
|
152
|
+
}
|
|
153
|
+
function dispose() {
|
|
154
|
+
const parked = waiters.length;
|
|
155
|
+
const outstanding = held.size;
|
|
156
|
+
while (waiters.length > 0) {
|
|
157
|
+
const w = waiters.shift();
|
|
158
|
+
if (w === undefined)
|
|
159
|
+
break;
|
|
160
|
+
disarm(w.timer);
|
|
161
|
+
w.grant();
|
|
162
|
+
}
|
|
163
|
+
for (const permit of held.values())
|
|
164
|
+
disarm(permit.ttlTimer);
|
|
165
|
+
held.clear();
|
|
166
|
+
if (parked > 0 || outstanding > 0) {
|
|
167
|
+
info(`fanout-ceiling: disposed — released ${parked} parked task(s) and disarmed ` +
|
|
168
|
+
`${outstanding} permit timer(s).`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
before,
|
|
173
|
+
after,
|
|
174
|
+
onSessionIdle,
|
|
175
|
+
dispose,
|
|
176
|
+
stats: () => ({
|
|
177
|
+
limit,
|
|
178
|
+
inFlight: held.size,
|
|
179
|
+
waiting: waiters.length,
|
|
180
|
+
waitedCount,
|
|
181
|
+
timedOutCount,
|
|
182
|
+
sweptCount,
|
|
183
|
+
ttlReclaimedCount,
|
|
184
|
+
armedTimers: armed.size,
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=subagent-fanout-ceiling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-fanout-ceiling.js","sourceRoot":"","sources":["../../src/hooks/subagent-fanout-ceiling.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { Plugin } from './opencode-types.js';
|
|
|
2
2
|
export declare const XemaPlugin: Plugin;
|
|
3
3
|
export declare function __resetBootCacheForTests(): void;
|
|
4
4
|
export default XemaPlugin;
|
|
5
|
+
export { createSubagentFanoutCeiling, resolveSubagentFanoutLimit, DEFAULT_SUBAGENT_FANOUT_LIMIT, DEFAULT_FANOUT_WAIT_TIMEOUT_MS, DEFAULT_PERMIT_TTL_MS, FANOUT_LIMIT_ENV, TASK_TOOL_ID, type SubagentFanoutCeiling, type FanoutCeilingOptions, type FanoutCeilingStats, } from './hooks/subagent-fanout-ceiling.js';
|
|
5
6
|
export { readInvocationContext } from './context.js';
|
|
6
7
|
export type { InvocationContext, InvocationRole } from './context.js';
|
|
7
8
|
export * from './errors.js';
|
|
8
|
-
export { loadSessionState, loadSessionStateForTool, ensureRoleAllowed, ContextMissingError, RoleNotAllowedError, type SessionState, } from './session-state.js';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqFA,OAAO,KAAK,EAIV,MAAM,EACP,MAAM,qBAAqB,CAAC;AAkB7B,eAAO,MAAM,UAAU,EAAE,MAgBxB,CAAC;AAmFF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA2BD,eAAe,UAAU,CAAC;AAG1B,OAAO,EACL,2BAA2B,EAC3B,0BAA0B,EAC1B,6BAA6B,EAC7B,8BAA8B,EAC9B,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtE,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { buildSessionIdleAutocommit } from './hooks/session-idle-autocommit.js';
|
|
4
|
+
import { createSubagentFanoutCeiling, resolveSubagentFanoutLimit, } from './hooks/subagent-fanout-ceiling.js';
|
|
4
5
|
import { buildSystemPromptOverlay } from './hooks/system-prompt-overlay.js';
|
|
5
|
-
import { info, isDebug } from './logger.js';
|
|
6
|
-
import { buildXemaMemoryGetTool, buildXemaMemorySetTool, } from './tools/xema-memory.js';
|
|
6
|
+
import { info, isDebug, warn } from './logger.js';
|
|
7
7
|
const bootCache = new Map();
|
|
8
8
|
export const XemaPlugin = async (ctx) => {
|
|
9
9
|
const { directory } = ctx;
|
|
@@ -22,31 +22,52 @@ export const XemaPlugin = async (ctx) => {
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
async function doBoot(directory) {
|
|
25
|
-
const tools = buildAllTools(directory);
|
|
26
|
-
const toolNames = Object.keys(tools).sort((a, b) => a.localeCompare(b));
|
|
27
25
|
const contextPath = join(directory, 'context.json');
|
|
28
26
|
const contextPresentAtBoot = existsSync(contextPath);
|
|
29
|
-
info(`boot: directory=${directory} tools
|
|
27
|
+
info(`boot: directory=${directory} tools=0 (hooks only) ` +
|
|
30
28
|
`context.json=${contextPresentAtBoot ? 'present' : 'absent'} ` +
|
|
31
29
|
`debug=${isDebug() ? 'on' : 'off'}`);
|
|
30
|
+
const fanoutLimit = resolveSubagentFanoutLimit();
|
|
31
|
+
const fanout = createSubagentFanoutCeiling({ limit: fanoutLimit });
|
|
32
|
+
const autocommit = buildSessionIdleAutocommit(directory);
|
|
33
|
+
info(`boot: subagent fan-out ceiling active (limit=${fanoutLimit} concurrent task calls ` +
|
|
34
|
+
`per worker; tighten with XEMA_SUBAGENT_FANOUT_LIMIT)`);
|
|
32
35
|
const hooks = {
|
|
33
|
-
tool: tools,
|
|
34
36
|
'experimental.chat.system.transform': buildSystemPromptOverlay(directory),
|
|
35
|
-
|
|
37
|
+
'tool.execute.before': async (input) => {
|
|
38
|
+
await fanout.before(input);
|
|
39
|
+
},
|
|
40
|
+
'tool.execute.after': (input) => {
|
|
41
|
+
fanout.after(input);
|
|
42
|
+
},
|
|
43
|
+
dispose: async () => {
|
|
44
|
+
fanout.dispose();
|
|
45
|
+
},
|
|
46
|
+
event: async (input) => {
|
|
47
|
+
const evt = input.event;
|
|
48
|
+
if (evt.type === 'session.idle') {
|
|
49
|
+
const sessionId = evt.properties
|
|
50
|
+
?.sessionID;
|
|
51
|
+
if (sessionId) {
|
|
52
|
+
try {
|
|
53
|
+
fanout.onSessionIdle(sessionId);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
warn(`fanout-ceiling: session-idle sweep failed: ${message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
await autocommit(input);
|
|
62
|
+
},
|
|
36
63
|
};
|
|
37
64
|
return hooks;
|
|
38
65
|
}
|
|
39
66
|
export function __resetBootCacheForTests() {
|
|
40
67
|
bootCache.clear();
|
|
41
68
|
}
|
|
42
|
-
function buildAllTools(workspaceDir) {
|
|
43
|
-
return {
|
|
44
|
-
xema_memory_set: buildXemaMemorySetTool(workspaceDir),
|
|
45
|
-
xema_memory_get: buildXemaMemoryGetTool(workspaceDir),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
69
|
export default XemaPlugin;
|
|
70
|
+
export { createSubagentFanoutCeiling, resolveSubagentFanoutLimit, DEFAULT_SUBAGENT_FANOUT_LIMIT, DEFAULT_FANOUT_WAIT_TIMEOUT_MS, DEFAULT_PERMIT_TTL_MS, FANOUT_LIMIT_ENV, TASK_TOOL_ID, } from './hooks/subagent-fanout-ceiling.js';
|
|
49
71
|
export { readInvocationContext } from './context.js';
|
|
50
72
|
export * from './errors.js';
|
|
51
|
-
export { loadSessionState, loadSessionStateForTool, ensureRoleAllowed, ContextMissingError, RoleNotAllowedError, } from './session-state.js';
|
|
52
73
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/opencode-types.d.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
import type { z } from 'zod';
|
|
2
|
-
interface ToolContext {
|
|
3
|
-
sessionID: string;
|
|
4
|
-
messageID: string;
|
|
5
|
-
agent: string;
|
|
6
|
-
directory: string;
|
|
7
|
-
worktree: string;
|
|
8
|
-
abort: AbortSignal;
|
|
9
|
-
metadata(input: {
|
|
10
|
-
title?: string;
|
|
11
|
-
metadata?: Record<string, unknown>;
|
|
12
|
-
}): void;
|
|
13
|
-
}
|
|
14
|
-
export interface ToolDefinition<Args extends z.ZodRawShape = z.ZodRawShape> {
|
|
15
|
-
description: string;
|
|
16
|
-
args: Args;
|
|
17
|
-
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<string>;
|
|
18
|
-
}
|
|
19
1
|
export interface ExperimentalChatModel {
|
|
20
2
|
providerID?: string;
|
|
21
3
|
modelID?: string;
|
|
@@ -37,8 +19,21 @@ export type OpencodeEvent = OpencodeSessionIdleEvent | {
|
|
|
37
19
|
type: string;
|
|
38
20
|
properties?: Record<string, unknown>;
|
|
39
21
|
};
|
|
22
|
+
export interface ToolExecuteHookInput {
|
|
23
|
+
tool: string;
|
|
24
|
+
sessionID: string;
|
|
25
|
+
callID: string;
|
|
26
|
+
}
|
|
40
27
|
export interface Hooks {
|
|
41
|
-
|
|
28
|
+
dispose?: () => Promise<void>;
|
|
29
|
+
'tool.execute.before'?: (input: ToolExecuteHookInput, output: {
|
|
30
|
+
args: unknown;
|
|
31
|
+
}) => Promise<void> | void;
|
|
32
|
+
'tool.execute.after'?: (input: ToolExecuteHookInput, output: {
|
|
33
|
+
title: string;
|
|
34
|
+
output: string;
|
|
35
|
+
metadata: unknown;
|
|
36
|
+
}) => Promise<void> | void;
|
|
42
37
|
'experimental.chat.system.transform'?: (input: ExperimentalChatSystemTransformInput, output: ExperimentalChatSystemTransformOutput) => Promise<void> | void;
|
|
43
38
|
event?: (input: {
|
|
44
39
|
event: OpencodeEvent;
|
|
@@ -50,6 +45,5 @@ interface PluginInput {
|
|
|
50
45
|
serverUrl: URL;
|
|
51
46
|
}
|
|
52
47
|
export type Plugin = (input: PluginInput) => Promise<Hooks>;
|
|
53
|
-
export declare function tool<Args extends z.ZodRawShape>(input: ToolDefinition<Args>): ToolDefinition<Args>;
|
|
54
48
|
export {};
|
|
55
49
|
//# sourceMappingURL=opencode-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-types.d.ts","sourceRoot":"","sources":["../src/opencode-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opencode-types.d.ts","sourceRoot":"","sources":["../src/opencode-types.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oCAAoC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,qBAAqB,CAAC;CAC9B;AAED,MAAM,WAAW,qCAAqC;IACpD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAUD,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,wBAAwB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAY3D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IAOpB,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAU9B,qBAAqB,CAAC,EAAE,CACtB,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KACtB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAO1B,oBAAoB,CAAC,EAAE,CACrB,KAAK,EAAE,oBAAoB,EAC3B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,KACzD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAO1B,oCAAoC,CAAC,EAAE,CACrC,KAAK,EAAE,oCAAoC,EAC3C,MAAM,EAAE,qCAAqC,KAC1C,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAQ1B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnE;AAED,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC"}
|
package/dist/opencode-types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-types.js","sourceRoot":"","sources":["../src/opencode-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opencode-types.js","sourceRoot":"","sources":["../src/opencode-types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/opencode-xema-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Opencode plugin providing Xema-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Opencode plugin providing the Xema dynamic system-prompt overlay hook, the per-turn session.idle auto-commit, and the sub-agent fan-out ceiling. Contributes no tools.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Neuralchowder Inc. <developer@xema.dev> (https://xema.dev)",
|
|
7
7
|
"homepage": "https://xema.dev",
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@opencode-ai/plugin": "^1.0.0",
|
|
27
|
-
"zod": "^4.0.0"
|
|
28
|
-
"@xemahq/kernel-contracts": ">=12.0.0"
|
|
27
|
+
"zod": "^4.0.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
30
|
"@opencode-ai/plugin": "^1.15.10",
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
"esbuild": "^0.25.4",
|
|
34
33
|
"typescript": "5.9.3",
|
|
35
34
|
"zod": "^4.3.6",
|
|
36
|
-
"@xemahq/
|
|
35
|
+
"@xemahq/contracts": "^0.1.0"
|
|
37
36
|
},
|
|
38
37
|
"exports": {
|
|
39
38
|
".": {
|