@xenosystem/blocks 0.6.0 → 0.8.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/dist/agent/host-source.d.ts +118 -0
- package/dist/agent/host-source.js +228 -0
- package/dist/agent/index.d.ts +568 -0
- package/dist/agent/index.js +1439 -0
- package/dist/auth/index.d.ts +400 -5
- package/dist/auth/index.js +621 -4
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +11 -11
- package/dist/edit/index.d.ts +6 -6
- package/dist/edit/index.js +14 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ops/index.d.ts +2 -2
- package/dist/ops/index.js +7 -7
- package/dist/time/index.d.ts +1 -1
- package/dist/time/index.js +4 -4
- package/dist/trust/index.d.ts +2 -2
- package/dist/trust/index.js +2 -2
- package/dist/types-C5T3uVwd.d.ts +425 -0
- package/package.json +11 -7
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { X as XenoAgentSessionDelta, a as XenoAgentEvent } from '../types-C5T3uVwd.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The reference adapter: XENO Agent host runtime events → this panel's `session` input.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this lives in the PANEL package
|
|
7
|
+
*
|
|
8
|
+
* A panel owns its input contract and a host adapts to it — that is how all 24 canonical panels
|
|
9
|
+
* work. But "the host adapts" left every host to rediscover the same three traps, and this package
|
|
10
|
+
* had already paid for all three while writing its own harness. So the reference adapter ships
|
|
11
|
+
* here, beside the contract it targets, as a separate subpath (`@xenosystem/blocks/agent/host-source`)
|
|
12
|
+
* that a renderer never imports.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 **It is written STRUCTURALLY and imports no host type.** `@xeno-corporation/xeno-agent-interface-contract`
|
|
15
|
+
* is not a dependency and must not become one: this package has zero runtime dependencies by a
|
|
16
|
+
* publish gate, and a panel that compiled against a host's types would invert the direction the
|
|
17
|
+
* whole catalogue depends on. The shapes below are what the host emits, restated as the minimum
|
|
18
|
+
* this adapter reads — and `translate` refuses anything it does not recognise rather than guessing.
|
|
19
|
+
*
|
|
20
|
+
* ## The three traps, all measured rather than anticipated
|
|
21
|
+
*
|
|
22
|
+
* 1. 🔴 **`rev` must ADVANCE per delta.** The controller drops `rev <= this.rev` **silently**, and
|
|
23
|
+
* `Date.now()` is not monotonic enough — three synchronous sends land in one millisecond, so two
|
|
24
|
+
* of every three deltas vanish and the failure surfaces somewhere unrelated. `createHostSource`
|
|
25
|
+
* owns a counter for exactly this reason; a caller never supplies `rev`.
|
|
26
|
+
* 2. 🔴 **`session` NESTS its payload under `session`; `turn`, `message` and `tool` are flat.**
|
|
27
|
+
* `INTEGRATION.md` documents the payload without showing the nesting, and the author of that
|
|
28
|
+
* document got it wrong reading it back.
|
|
29
|
+
* 3. 🔴 **A streamed reply is ONE record grown by `append`, never one per chunk.** One event per
|
|
30
|
+
* token defeats the console's adjacent-duplicate collapsing and is the reason the console gained
|
|
31
|
+
* an update-by-id channel at all. `assistantDelta` opens with `text` once and `append`s after.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ **Unrecognised events are COUNTED, not dropped silently.** A host whose vocabulary drifts must
|
|
34
|
+
* be able to see that it drifted — a source that quietly ignores half its input looks identical to
|
|
35
|
+
* a quiet agent.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** The host envelope, restated as the fields this adapter reads. Structural on purpose. */
|
|
39
|
+
interface HostRuntimeEvent {
|
|
40
|
+
readonly conversationId: string;
|
|
41
|
+
readonly requestId?: string;
|
|
42
|
+
readonly type: string;
|
|
43
|
+
readonly providerId?: string;
|
|
44
|
+
readonly parentRunId?: string;
|
|
45
|
+
readonly subagentRunId?: string;
|
|
46
|
+
readonly timestamp?: string;
|
|
47
|
+
readonly event?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A conversation as the host DESCRIBES it, not as it streams.
|
|
51
|
+
*
|
|
52
|
+
* 🔴 **The runtime event stream carries no title.** Measured against
|
|
53
|
+
* `@xeno-corporation/xeno-agent-interface-contract`: a conversation's `title` lives on the
|
|
54
|
+
* conversation RECORD returned by `conversation.open` / `conversation.list`, and appears on no
|
|
55
|
+
* runtime envelope. A host that forwards only the event stream therefore shows every conversation
|
|
56
|
+
* unnamed — which is what happened the first time this chain ran end to end, and which the first
|
|
57
|
+
* fix made worse by promoting a tool's `label` into the title (see `translate`).
|
|
58
|
+
*
|
|
59
|
+
* So the record is its own entry point. Fields the host does not know are simply absent.
|
|
60
|
+
*/
|
|
61
|
+
interface HostConversationRecord {
|
|
62
|
+
readonly id: string;
|
|
63
|
+
readonly title?: string;
|
|
64
|
+
readonly providerId?: string;
|
|
65
|
+
/** `cloud` | `sdk-native` | `acp` — the panel renders it, it does not interpret it. */
|
|
66
|
+
readonly lane?: string;
|
|
67
|
+
}
|
|
68
|
+
interface HostSourceOptions {
|
|
69
|
+
/** Called with each delta, in order. Wire this to `deliverInput('session', delta)`. */
|
|
70
|
+
readonly emit: (delta: XenoAgentSessionDelta) => void;
|
|
71
|
+
/** Injectable for tests. Never used for `rev` — see trap 1. */
|
|
72
|
+
readonly now?: () => number;
|
|
73
|
+
}
|
|
74
|
+
interface HostSourceStats {
|
|
75
|
+
/** Events this adapter understood and translated. */
|
|
76
|
+
readonly translated: number;
|
|
77
|
+
/** Events it did not recognise, by `type`. Reported, never silently dropped. */
|
|
78
|
+
readonly unrecognised: Readonly<Record<string, number>>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The host frame, before any of this can read it.
|
|
82
|
+
*
|
|
83
|
+
* 🔴 **A host event is THREE layers deep, and the first version of this adapter read the top one.**
|
|
84
|
+
* Measured against a running host on 2026-09-06 — real `AgentHostCoordinator`, real ACP turn
|
|
85
|
+
* adapter, real socket — a turn produced 13 events and this producer translated **zero**:
|
|
86
|
+
*
|
|
87
|
+
* ```
|
|
88
|
+
* outer { type: "runtime.event.appended", source: "host", payload: { requestId, event: … } }
|
|
89
|
+
* ↓
|
|
90
|
+
* runtime { conversationId, requestId, providerId, providerKind, type: "acp.…", event: … }
|
|
91
|
+
* ↓
|
|
92
|
+
* payload { messageId, text, … } ← the fields this adapter actually reads
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* The MIDDLE layer is `HostRuntimeEvent`, so the structural assumption was right and the entry
|
|
96
|
+
* point was wrong. Unwrapping is done here rather than by every consumer, because a consumer that
|
|
97
|
+
* forgets it sees a silent agent — not an error.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ Not every outer event wraps a runtime event: `turn.execution.completed` carries its
|
|
100
|
+
* conversation and request ids directly on `payload` and has no inner event at all. That one is
|
|
101
|
+
* the turn's terminal signal, so dropping it would leave a finished turn spinning forever.
|
|
102
|
+
*/
|
|
103
|
+
declare function unwrapHostFrame(frame: Record<string, unknown>): HostRuntimeEvent | null;
|
|
104
|
+
/**
|
|
105
|
+
* Translate ONE host event into zero or more panel events.
|
|
106
|
+
*
|
|
107
|
+
* Pure and exported so a host can test its own vocabulary against this without a panel, a socket,
|
|
108
|
+
* or a running agent.
|
|
109
|
+
*/
|
|
110
|
+
declare function translate(envelope: HostRuntimeEvent): XenoAgentEvent[];
|
|
111
|
+
declare function createHostSource(options: HostSourceOptions): {
|
|
112
|
+
push: (frame: HostRuntimeEvent | Record<string, unknown>) => void;
|
|
113
|
+
session: (record: HostConversationRecord) => void;
|
|
114
|
+
stats: () => HostSourceStats;
|
|
115
|
+
reset: () => void;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { type HostConversationRecord, type HostRuntimeEvent, type HostSourceOptions, type HostSourceStats, createHostSource, translate, unwrapHostFrame };
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import "../chunk-2KG3PWR4.js";
|
|
2
|
+
|
|
3
|
+
// src/agent/agent/hostSource.ts
|
|
4
|
+
var str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
5
|
+
function turnStateOf(type, event) {
|
|
6
|
+
if (type === "running" || type === "turn.started") return "running";
|
|
7
|
+
if (type === "turn.completed" || type === "completed") return "succeeded";
|
|
8
|
+
if (type === "turn.failed" || type === "failed") return "failed";
|
|
9
|
+
if (type === "turn.cancelled" || type === "cancelled") return "cancelled";
|
|
10
|
+
if (type === "turn.execution.completed") return "succeeded";
|
|
11
|
+
if (type === "turn.execution.failed") return "failed";
|
|
12
|
+
const state = str(event.state);
|
|
13
|
+
if (type === "turn" && state) return state;
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
function elicitationOf(type, event) {
|
|
17
|
+
if (!type.startsWith("elicitation.")) return null;
|
|
18
|
+
if (type.endsWith(".requested")) {
|
|
19
|
+
const ask = event.elicitation ?? event.request ?? event;
|
|
20
|
+
return { ask };
|
|
21
|
+
}
|
|
22
|
+
if (type.endsWith(".answer")) {
|
|
23
|
+
const id = str(event.permissionId) ?? str(event.askUserId) ?? str(event.chooseDirectoryId) ?? str(event.workspacePlacementId) ?? str(event.id);
|
|
24
|
+
return id ? { withdrawId: id } : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function unwrapHostFrame(frame) {
|
|
29
|
+
const type = str(frame.type);
|
|
30
|
+
if (!type) return null;
|
|
31
|
+
const payload = frame.payload ?? {};
|
|
32
|
+
if (type === "runtime.event.appended") {
|
|
33
|
+
const inner = payload.event;
|
|
34
|
+
return inner && typeof inner === "object" ? inner : null;
|
|
35
|
+
}
|
|
36
|
+
if (str(frame.conversationId)) return frame;
|
|
37
|
+
const conversationId = str(payload.conversationId) ?? str(frame.conversationId);
|
|
38
|
+
if (!conversationId) return null;
|
|
39
|
+
return {
|
|
40
|
+
conversationId,
|
|
41
|
+
...str(payload.requestId) ?? str(frame.requestId) ? { requestId: str(payload.requestId) ?? str(frame.requestId) } : {},
|
|
42
|
+
type,
|
|
43
|
+
event: payload
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function fromAcp(type, event) {
|
|
47
|
+
if (type === "acp.session.message.delta" || type === "acp.session.message") {
|
|
48
|
+
const messageId = str(event.messageId);
|
|
49
|
+
if (!messageId) return null;
|
|
50
|
+
const text = str(event.text) ?? "";
|
|
51
|
+
return { message: { messageId, append: text, role: "assistant" } };
|
|
52
|
+
}
|
|
53
|
+
if (type === "acp.session.plan") {
|
|
54
|
+
const content = str(event.content);
|
|
55
|
+
if (!content) return { ignored: true };
|
|
56
|
+
return { message: { messageId: `plan-${str(event.id) ?? "current"}`, text: content, role: "system" } };
|
|
57
|
+
}
|
|
58
|
+
if (type.startsWith("acp.tool.")) {
|
|
59
|
+
const callId = str(event.toolCallId);
|
|
60
|
+
if (!callId) return null;
|
|
61
|
+
const status = str(event.status) ?? "";
|
|
62
|
+
const state = type === "acp.tool.completed" || status === "completed" ? status === "failed" ? "failed" : "succeeded" : status === "failed" ? "failed" : "running";
|
|
63
|
+
return {
|
|
64
|
+
tool: {
|
|
65
|
+
callId,
|
|
66
|
+
// `title` is what a person reads; `toolName` is the tool's id. Later updates carry only the
|
|
67
|
+
// latter, and the panel keeps the label it was opened with, so passing both is correct.
|
|
68
|
+
...str(event.title) ?? str(event.toolName) ? { label: str(event.title) ?? str(event.toolName) } : {},
|
|
69
|
+
state,
|
|
70
|
+
...str(event.kind) ? { detail: { kind: str(event.kind) } } : {}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (type === "acp.session.completed") {
|
|
75
|
+
return { turn: str(event.status) === "failed" ? "failed" : "succeeded" };
|
|
76
|
+
}
|
|
77
|
+
if (type === "acp.session.failed" || type === "acp.agent.failed") return { turn: "failed" };
|
|
78
|
+
if (type === "acp.agent.started" || type === "acp.session.started") return { turn: "running" };
|
|
79
|
+
if (type === "acp.session.usage" || type === "acp.session.thought") return { ignored: true };
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function translate(envelope) {
|
|
83
|
+
const sessionId = envelope.conversationId;
|
|
84
|
+
if (!sessionId) return [];
|
|
85
|
+
const event = envelope.event ?? {};
|
|
86
|
+
const type = envelope.type;
|
|
87
|
+
const out = [];
|
|
88
|
+
out.push({
|
|
89
|
+
type: "session",
|
|
90
|
+
session: {
|
|
91
|
+
id: sessionId,
|
|
92
|
+
title: str(event.title),
|
|
93
|
+
providerId: envelope.providerId
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const turnId = envelope.requestId;
|
|
97
|
+
const state = turnStateOf(type, event);
|
|
98
|
+
if (turnId && state) {
|
|
99
|
+
out.push({ type: "turn", sessionId, turnId, state, label: str(event.label) });
|
|
100
|
+
}
|
|
101
|
+
const elicitation = elicitationOf(type, event);
|
|
102
|
+
if (elicitation) {
|
|
103
|
+
if ("ask" in elicitation) out.push({ type: "ask", sessionId, ask: elicitation.ask });
|
|
104
|
+
else out.push({ type: "ask-withdraw", sessionId, askId: elicitation.withdrawId });
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
const acp = type.startsWith("acp.") ? fromAcp(type, event) : null;
|
|
108
|
+
if (acp) {
|
|
109
|
+
if (acp.turn && turnId) {
|
|
110
|
+
out.push({ type: "turn", sessionId, turnId, state: acp.turn, label: str(event.title) });
|
|
111
|
+
}
|
|
112
|
+
if (acp.message) {
|
|
113
|
+
out.push({
|
|
114
|
+
type: "message",
|
|
115
|
+
sessionId,
|
|
116
|
+
turnId,
|
|
117
|
+
messageId: acp.message.messageId,
|
|
118
|
+
role: acp.message.role,
|
|
119
|
+
...acp.message.append !== void 0 ? { append: acp.message.append } : { text: acp.message.text ?? "" }
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (acp.tool && turnId) {
|
|
123
|
+
out.push({
|
|
124
|
+
type: "tool",
|
|
125
|
+
sessionId,
|
|
126
|
+
turnId,
|
|
127
|
+
callId: acp.tool.callId,
|
|
128
|
+
parentCallId: envelope.parentRunId,
|
|
129
|
+
...acp.tool.label ? { label: acp.tool.label } : {},
|
|
130
|
+
state: acp.tool.state,
|
|
131
|
+
...acp.tool.detail ? { detail: acp.tool.detail } : {}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
if (type === "assistant" || type === "message") {
|
|
137
|
+
const messageId = str(event.messageId) ?? str(event.id) ?? turnId;
|
|
138
|
+
if (messageId) {
|
|
139
|
+
const append = str(event.delta) ?? str(event.append);
|
|
140
|
+
out.push({
|
|
141
|
+
type: "message",
|
|
142
|
+
sessionId,
|
|
143
|
+
turnId,
|
|
144
|
+
messageId,
|
|
145
|
+
role: str(event.role) ?? "assistant",
|
|
146
|
+
// Trap 3: `append` is the streaming channel; `text` opens or replaces.
|
|
147
|
+
...append !== void 0 ? { append } : { text: str(event.text) ?? "" }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
if (type === "tool" || type.startsWith("tools.")) {
|
|
153
|
+
const callId = str(event.callId) ?? str(event.toolCallId) ?? str(event.id);
|
|
154
|
+
if (callId && turnId) {
|
|
155
|
+
out.push({
|
|
156
|
+
type: "tool",
|
|
157
|
+
sessionId,
|
|
158
|
+
turnId,
|
|
159
|
+
callId,
|
|
160
|
+
// A subagent's call nests under the call that spawned it — the recursion `runs` gained.
|
|
161
|
+
parentCallId: envelope.parentRunId,
|
|
162
|
+
label: str(event.label) ?? str(event.name) ?? type.replace(/^tools\./, ""),
|
|
163
|
+
state: str(event.state) ?? "running",
|
|
164
|
+
detail: str(event.detail),
|
|
165
|
+
error: str(event.error)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
var IGNORED_TYPES = /* @__PURE__ */ new Set(["acp.session.usage", "acp.session.thought"]);
|
|
173
|
+
function createHostSource(options) {
|
|
174
|
+
let rev = 0;
|
|
175
|
+
let translated = 0;
|
|
176
|
+
const unrecognised = {};
|
|
177
|
+
return {
|
|
178
|
+
push(frame) {
|
|
179
|
+
const envelope = unwrapHostFrame(frame) ?? frame;
|
|
180
|
+
const events = translate(envelope);
|
|
181
|
+
const type = envelope.type ?? frame.type;
|
|
182
|
+
if (events.length <= 1 && type && !IGNORED_TYPES.has(type)) {
|
|
183
|
+
unrecognised[type] = (unrecognised[type] ?? 0) + 1;
|
|
184
|
+
} else {
|
|
185
|
+
translated += 1;
|
|
186
|
+
}
|
|
187
|
+
if (events.length === 0) return;
|
|
188
|
+
rev += 1;
|
|
189
|
+
options.emit({ rev, events });
|
|
190
|
+
},
|
|
191
|
+
/*
|
|
192
|
+
* Upsert a conversation from the host's own DESCRIPTION of it.
|
|
193
|
+
*
|
|
194
|
+
* ⚠️ Deliberately NOT counted as `translated` — that statistic answers "did this adapter
|
|
195
|
+
* understand the host's event VOCABULARY", and a record the host handed over directly proves
|
|
196
|
+
* nothing about the stream. Conflating them would let a source that understood no events at
|
|
197
|
+
* all report a healthy translation count.
|
|
198
|
+
*/
|
|
199
|
+
session(record) {
|
|
200
|
+
if (!record.id) return;
|
|
201
|
+
rev += 1;
|
|
202
|
+
options.emit({
|
|
203
|
+
rev,
|
|
204
|
+
events: [
|
|
205
|
+
{
|
|
206
|
+
type: "session",
|
|
207
|
+
session: {
|
|
208
|
+
id: record.id,
|
|
209
|
+
title: record.title,
|
|
210
|
+
providerId: record.providerId,
|
|
211
|
+
lane: record.lane
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
stats: () => ({ translated, unrecognised: { ...unrecognised } }),
|
|
218
|
+
reset() {
|
|
219
|
+
translated = 0;
|
|
220
|
+
for (const key of Object.keys(unrecognised)) delete unrecognised[key];
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export {
|
|
225
|
+
createHostSource,
|
|
226
|
+
translate,
|
|
227
|
+
unwrapHostFrame
|
|
228
|
+
};
|