create-windy 0.6.18 → 0.6.20
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/cli.js +133 -54
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +5 -2
- package/template/apps/agent-server/README.md +6 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +23 -0
- package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +9 -0
- package/template/apps/agent-server/src/southwind_agent_server/runtime_compatibility.py +89 -0
- package/template/apps/agent-server/tests/test_runtime_compatibility.py +119 -0
- package/template/apps/server/src/agent/remote-runtime-compatibility.test.ts +147 -0
- package/template/apps/server/src/agent/remote-runtime.ts +66 -4
- package/template/apps/server/src/agent/runtime-bootstrap.ts +1 -0
- package/template/apps/web/src/components/common/FormDialog.vue +1 -0
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +1 -0
- package/template/apps/web/src/components/ui/dialog/DialogContent.vue +1 -1
- package/template/apps/web/src/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +1 -0
- package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.vue +182 -0
- package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.webtest.ts +93 -0
- package/template/apps/web/src/pages/system/components/user-account/RolePermissionSummary.vue +59 -0
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +21 -71
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.webtest.ts +71 -0
- package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.vue +48 -27
- package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts +30 -1
- package/template/docs/architecture/agent-runtime-compatibility.md +107 -0
- package/template/docs/architecture/ai-runtime.md +8 -15
- package/template/docs/platform/agent-runtime.md +5 -0
- package/template/packages/server-sdk/contracts/agent-runtime-compatibility/v1/offer.json +22 -0
- package/template/packages/server-sdk/index.ts +19 -0
- package/template/packages/server-sdk/package.json +2 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.contract.test.ts +143 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.public-api.test.ts +19 -0
- package/template/packages/server-sdk/src/agent-runtime-compatibility.ts +284 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 1 as const;
|
|
4
|
+
export const AGENT_RUNTIME_PROTOCOL_VERSION_V1 = 1 as const;
|
|
5
|
+
export const AGENT_RUNTIME_KIND_V1 = "agent" as const;
|
|
6
|
+
export const AGENT_RUNTIME_COMPATIBILITY_PATH_V1 =
|
|
7
|
+
"/api/agent/runtime/compatibility" as const;
|
|
8
|
+
export const AGENT_RUNTIME_PROTOCOL_HEADER_V1 =
|
|
9
|
+
"X-Windy-Agent-Runtime-Protocol" as const;
|
|
10
|
+
export const AGENT_RUNTIME_OFFER_DIGEST_HEADER_V1 =
|
|
11
|
+
"X-Windy-Agent-Runtime-Offer-Digest" as const;
|
|
12
|
+
export const AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1 = 1 as const;
|
|
13
|
+
export const AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1 =
|
|
14
|
+
"windy-internal-bearer-v1" as const;
|
|
15
|
+
|
|
16
|
+
export const AGENT_RUNTIME_PROTOCOL_FEATURES_V1 = Object.freeze([
|
|
17
|
+
"canonical-sse-replay.v1",
|
|
18
|
+
"execution-grant-forwarding.v1",
|
|
19
|
+
"operation-release-attestation.v1",
|
|
20
|
+
"run-cancel.v1",
|
|
21
|
+
"run-lifecycle.v1",
|
|
22
|
+
"runtime-reporting.v1",
|
|
23
|
+
"tool-callback.v1",
|
|
24
|
+
] as const);
|
|
25
|
+
|
|
26
|
+
export type AgentRuntimeCompatibilityDigestV1 = `sha256:${string}`;
|
|
27
|
+
|
|
28
|
+
export interface AgentRuntimeCompatibilityOfferV1 {
|
|
29
|
+
readonly schemaVersion: typeof AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION;
|
|
30
|
+
readonly runtimeKind: typeof AGENT_RUNTIME_KIND_V1;
|
|
31
|
+
readonly engineId: string;
|
|
32
|
+
readonly protocolVersions: readonly number[];
|
|
33
|
+
readonly protocolFeatures: readonly string[];
|
|
34
|
+
readonly toolCallback: {
|
|
35
|
+
readonly contractVersion: typeof AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1;
|
|
36
|
+
readonly authentication: string;
|
|
37
|
+
};
|
|
38
|
+
readonly releaseDigest: AgentRuntimeCompatibilityDigestV1;
|
|
39
|
+
readonly capabilitiesDigest: AgentRuntimeCompatibilityDigestV1;
|
|
40
|
+
readonly offerDigest: AgentRuntimeCompatibilityDigestV1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AgentRuntimeCompatibilityRequirementsV1 {
|
|
44
|
+
readonly protocolVersions: readonly number[];
|
|
45
|
+
readonly requiredProtocolFeatures: readonly string[];
|
|
46
|
+
readonly requiredToolCallbackAuthentication: string;
|
|
47
|
+
readonly expectedReleaseDigest?: AgentRuntimeCompatibilityDigestV1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type AgentRuntimeCompatibilityNegotiationV1 =
|
|
51
|
+
| {
|
|
52
|
+
readonly ok: true;
|
|
53
|
+
readonly protocolVersion: number;
|
|
54
|
+
readonly offer: AgentRuntimeCompatibilityOfferV1;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
readonly ok: false;
|
|
58
|
+
readonly code:
|
|
59
|
+
| "CONTRACT_INVALID"
|
|
60
|
+
| "CONTRACT_UNSUPPORTED"
|
|
61
|
+
| "CAPABILITY_MISSING"
|
|
62
|
+
| "CALLBACK_AUTH_UNSUPPORTED"
|
|
63
|
+
| "RELEASE_DIGEST_MISMATCH";
|
|
64
|
+
readonly subject?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stableValuePattern = /^[a-z0-9][a-z0-9._-]{0,159}$/;
|
|
68
|
+
|
|
69
|
+
export function createAgentRuntimeCompatibilityOfferV1(
|
|
70
|
+
engineId: string,
|
|
71
|
+
): AgentRuntimeCompatibilityOfferV1 {
|
|
72
|
+
const normalizedEngineId = stableValue(engineId);
|
|
73
|
+
if (!normalizedEngineId) {
|
|
74
|
+
throw new TypeError("Agent Runtime engineId invalid");
|
|
75
|
+
}
|
|
76
|
+
return buildOffer({
|
|
77
|
+
engineId: normalizedEngineId,
|
|
78
|
+
protocolVersions: [AGENT_RUNTIME_PROTOCOL_VERSION_V1],
|
|
79
|
+
protocolFeatures: AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
80
|
+
toolCallback: {
|
|
81
|
+
contractVersion: AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1,
|
|
82
|
+
authentication: AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parseAgentRuntimeCompatibilityOfferV1(
|
|
88
|
+
raw: unknown,
|
|
89
|
+
): AgentRuntimeCompatibilityOfferV1 | null {
|
|
90
|
+
try {
|
|
91
|
+
return parseCapturedOffer(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseCapturedOffer(
|
|
98
|
+
raw: unknown,
|
|
99
|
+
): AgentRuntimeCompatibilityOfferV1 | null {
|
|
100
|
+
if (!isRecord(raw)) return null;
|
|
101
|
+
if (
|
|
102
|
+
raw.schemaVersion !== AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION ||
|
|
103
|
+
raw.runtimeKind !== AGENT_RUNTIME_KIND_V1
|
|
104
|
+
) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const engineId = stableValue(raw.engineId);
|
|
108
|
+
const protocolVersions = versions(raw.protocolVersions);
|
|
109
|
+
const protocolFeatures = stableValues(raw.protocolFeatures, 64);
|
|
110
|
+
const toolCallback = parseToolCallback(raw.toolCallback);
|
|
111
|
+
if (!engineId || !protocolVersions || !protocolFeatures || !toolCallback) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const expected = buildOffer({
|
|
115
|
+
engineId,
|
|
116
|
+
protocolVersions,
|
|
117
|
+
protocolFeatures,
|
|
118
|
+
toolCallback,
|
|
119
|
+
});
|
|
120
|
+
return raw.releaseDigest === expected.releaseDigest &&
|
|
121
|
+
raw.capabilitiesDigest === expected.capabilitiesDigest &&
|
|
122
|
+
raw.offerDigest === expected.offerDigest
|
|
123
|
+
? expected
|
|
124
|
+
: null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function negotiateAgentRuntimeCompatibilityV1(
|
|
128
|
+
raw: unknown,
|
|
129
|
+
requirements: AgentRuntimeCompatibilityRequirementsV1,
|
|
130
|
+
): AgentRuntimeCompatibilityNegotiationV1 {
|
|
131
|
+
const schemaVersion = observedSchemaVersion(raw);
|
|
132
|
+
if (
|
|
133
|
+
schemaVersion !== undefined &&
|
|
134
|
+
schemaVersion !== AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION
|
|
135
|
+
) {
|
|
136
|
+
return { ok: false, code: "CONTRACT_UNSUPPORTED" };
|
|
137
|
+
}
|
|
138
|
+
const offer = parseAgentRuntimeCompatibilityOfferV1(raw);
|
|
139
|
+
if (!offer) return { ok: false, code: "CONTRACT_INVALID" };
|
|
140
|
+
const requiredVersions = versions(requirements.protocolVersions);
|
|
141
|
+
const requiredFeatures = stableValues(
|
|
142
|
+
requirements.requiredProtocolFeatures,
|
|
143
|
+
64,
|
|
144
|
+
);
|
|
145
|
+
const callbackAuthentication = stableValue(
|
|
146
|
+
requirements.requiredToolCallbackAuthentication,
|
|
147
|
+
);
|
|
148
|
+
if (!requiredVersions || !requiredFeatures || !callbackAuthentication) {
|
|
149
|
+
throw new TypeError("Agent Runtime compatibility requirements invalid");
|
|
150
|
+
}
|
|
151
|
+
const protocolVersion = [...offer.protocolVersions]
|
|
152
|
+
.filter((version) => requiredVersions.includes(version))
|
|
153
|
+
.sort((left, right) => right - left)[0];
|
|
154
|
+
if (protocolVersion === undefined) {
|
|
155
|
+
return { ok: false, code: "CONTRACT_UNSUPPORTED" };
|
|
156
|
+
}
|
|
157
|
+
const missing = requiredFeatures.find(
|
|
158
|
+
(feature) => !offer.protocolFeatures.includes(feature),
|
|
159
|
+
);
|
|
160
|
+
if (missing) {
|
|
161
|
+
return { ok: false, code: "CAPABILITY_MISSING", subject: missing };
|
|
162
|
+
}
|
|
163
|
+
if (offer.toolCallback.authentication !== callbackAuthentication) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
code: "CALLBACK_AUTH_UNSUPPORTED",
|
|
167
|
+
subject: callbackAuthentication,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
requirements.expectedReleaseDigest &&
|
|
172
|
+
requirements.expectedReleaseDigest !== offer.releaseDigest
|
|
173
|
+
) {
|
|
174
|
+
return { ok: false, code: "RELEASE_DIGEST_MISMATCH" };
|
|
175
|
+
}
|
|
176
|
+
return { ok: true, protocolVersion, offer };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function localAgentRuntimeCompatibilityRequirementsV1(): AgentRuntimeCompatibilityRequirementsV1 {
|
|
180
|
+
return {
|
|
181
|
+
protocolVersions: [AGENT_RUNTIME_PROTOCOL_VERSION_V1],
|
|
182
|
+
requiredProtocolFeatures: AGENT_RUNTIME_PROTOCOL_FEATURES_V1,
|
|
183
|
+
requiredToolCallbackAuthentication: AGENT_RUNTIME_INTERNAL_CALLBACK_AUTH_V1,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildOffer(input: {
|
|
188
|
+
readonly engineId: string;
|
|
189
|
+
readonly protocolVersions: readonly number[];
|
|
190
|
+
readonly protocolFeatures: readonly string[];
|
|
191
|
+
readonly toolCallback: AgentRuntimeCompatibilityOfferV1["toolCallback"];
|
|
192
|
+
}): AgentRuntimeCompatibilityOfferV1 {
|
|
193
|
+
const protocolVersions = sortedNumbers(input.protocolVersions);
|
|
194
|
+
const protocolFeatures = sortedStrings(input.protocolFeatures);
|
|
195
|
+
const releaseDigest = digest({
|
|
196
|
+
runtimeKind: AGENT_RUNTIME_KIND_V1,
|
|
197
|
+
engineId: input.engineId,
|
|
198
|
+
protocolVersions,
|
|
199
|
+
});
|
|
200
|
+
const capabilitiesDigest = digest({
|
|
201
|
+
protocolFeatures,
|
|
202
|
+
toolCallback: input.toolCallback,
|
|
203
|
+
});
|
|
204
|
+
const body = {
|
|
205
|
+
schemaVersion: AGENT_RUNTIME_COMPATIBILITY_SCHEMA_VERSION,
|
|
206
|
+
runtimeKind: AGENT_RUNTIME_KIND_V1,
|
|
207
|
+
engineId: input.engineId,
|
|
208
|
+
protocolVersions,
|
|
209
|
+
protocolFeatures,
|
|
210
|
+
toolCallback: { ...input.toolCallback },
|
|
211
|
+
releaseDigest,
|
|
212
|
+
capabilitiesDigest,
|
|
213
|
+
};
|
|
214
|
+
return { ...body, offerDigest: digest(body) };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function digest(value: unknown): AgentRuntimeCompatibilityDigestV1 {
|
|
218
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function parseToolCallback(
|
|
222
|
+
raw: unknown,
|
|
223
|
+
): AgentRuntimeCompatibilityOfferV1["toolCallback"] | null {
|
|
224
|
+
if (!isRecord(raw)) return null;
|
|
225
|
+
const authentication = stableValue(raw.authentication);
|
|
226
|
+
return raw.contractVersion ===
|
|
227
|
+
AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1 && authentication
|
|
228
|
+
? {
|
|
229
|
+
contractVersion: AGENT_RUNTIME_TOOL_CALLBACK_CONTRACT_VERSION_V1,
|
|
230
|
+
authentication,
|
|
231
|
+
}
|
|
232
|
+
: null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function versions(raw: unknown): readonly number[] | null {
|
|
236
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 16) return null;
|
|
237
|
+
if (
|
|
238
|
+
raw.some(
|
|
239
|
+
(value) =>
|
|
240
|
+
!Number.isSafeInteger(value) ||
|
|
241
|
+
Number(value) < 1 ||
|
|
242
|
+
Number(value) > 255,
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const result = sortedNumbers(raw as number[]);
|
|
248
|
+
return result.length === raw.length ? result : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function stableValues(raw: unknown, limit: number): readonly string[] | null {
|
|
252
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > limit)
|
|
253
|
+
return null;
|
|
254
|
+
const values = raw.map(stableValue);
|
|
255
|
+
if (values.some((value) => !value)) return null;
|
|
256
|
+
const result = sortedStrings(values as string[]);
|
|
257
|
+
return result.length === raw.length ? result : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function stableValue(raw: unknown): string | null {
|
|
261
|
+
return typeof raw === "string" && stableValuePattern.test(raw) ? raw : null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sortedNumbers(values: readonly number[]): readonly number[] {
|
|
265
|
+
return [...new Set(values)].sort((left, right) => left - right);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function sortedStrings(values: readonly string[]): readonly string[] {
|
|
269
|
+
return [...new Set(values)].sort((left, right) =>
|
|
270
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
275
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function observedSchemaVersion(raw: unknown): unknown {
|
|
279
|
+
try {
|
|
280
|
+
return isRecord(raw) ? raw.schemaVersion : undefined;
|
|
281
|
+
} catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|