@mawaru/sdk 0.38.0 → 0.40.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/_schemas/ai-manifest.test.ts +8 -0
- package/_schemas/ai-manifest.ts +2 -0
- package/_schemas/graph.test.ts +51 -9
- package/_schemas/graph.ts +23 -8
- package/_schemas/human-manifest.test.ts +129 -0
- package/_schemas/human-manifest.ts +158 -0
- package/_schemas/node.ts +4 -0
- package/_schemas/port-spec.test.ts +164 -40
- package/_schemas/port-spec.ts +77 -26
- package/dist/_cli/login.js +1 -15
- package/dist/_cli/openBrowser.d.ts +1 -0
- package/dist/_cli/openBrowser.js +15 -0
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +8 -0
- package/dist/_cli/previewCommand.js +95 -37
- package/dist/_cli/previewFiles.d.ts +2 -1
- package/dist/_cli/previewFiles.js +42 -19
- package/dist/_cli/previewSlack.d.ts +1 -1
- package/dist/_cli/previewSlack.js +1 -1
- package/dist/_schemas/ai-manifest.d.ts +3 -0
- package/dist/_schemas/ai-manifest.js +2 -0
- package/dist/_schemas/graph.d.ts +23 -3
- package/dist/_schemas/graph.js +20 -8
- package/dist/_schemas/human-manifest.d.ts +27 -0
- package/dist/_schemas/human-manifest.js +121 -0
- package/dist/_schemas/node.d.ts +1 -0
- package/dist/_schemas/node.js +4 -0
- package/dist/_schemas/port-spec.d.ts +8 -9
- package/dist/_schemas/port-spec.js +64 -9
- package/dist/cli.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +22 -3
- package/dist/typegen.d.ts +3 -1
- package/dist/typegen.js +96 -14
- package/dist/validate.js +55 -0
- package/dist/views/_approval/ApprovalBar.d.ts +20 -0
- package/dist/views/_approval/ApprovalBar.js +74 -0
- package/dist/views/_approvalActions.d.ts +18 -0
- package/dist/views/_approvalActions.js +10 -0
- package/dist/views/_bridge.d.ts +12 -0
- package/dist/views/_bridge.js +10 -0
- package/dist/views/_parts/DocumentCard.d.ts +2 -0
- package/dist/views/_parts/DocumentCard.js +16 -0
- package/dist/views/_parts/FilePreview.d.ts +5 -0
- package/dist/views/_parts/FilePreview.js +101 -0
- package/dist/views/_parts/Linkify.d.ts +7 -0
- package/dist/views/_parts/Linkify.js +28 -0
- package/dist/views/_parts/Markdown.d.ts +44 -0
- package/dist/views/_parts/Markdown.js +313 -0
- package/dist/views/_parts/SchemaForm.d.ts +19 -0
- package/dist/views/_parts/SchemaForm.js +310 -0
- package/dist/views/_parts/fileRef.d.ts +7 -0
- package/dist/views/_parts/fileRef.js +11 -0
- package/dist/views/_parts/useNarrow.d.ts +5 -0
- package/dist/views/_parts/useNarrow.js +23 -0
- package/dist/views/host.d.ts +18 -0
- package/dist/views/host.js +20 -0
- package/dist/views/human.d.ts +73 -0
- package/dist/views/human.js +6 -0
- package/dist/views/index.d.ts +8 -0
- package/dist/views/index.js +12 -0
- package/dist/views/protocol.d.ts +34 -0
- package/dist/views/protocol.js +5 -0
- package/dist/views/runtime.d.ts +3 -0
- package/dist/views/runtime.js +86 -0
- package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
- package/docs/development.md +45 -5
- package/index.ts +1 -0
- package/package.json +36 -4
- package/skills/create-loop/SKILL.md +6 -3
- package/views/_approval/ApprovalBar.tsx +239 -0
- package/views/_approvalActions.tsx +31 -0
- package/views/_bridge.tsx +36 -0
- package/views/_parts/DocumentCard.ts +18 -0
- package/views/_parts/FilePreview.tsx +180 -0
- package/views/_parts/Linkify.test.ts +40 -0
- package/views/_parts/Linkify.tsx +40 -0
- package/views/_parts/Markdown.test.ts +131 -0
- package/views/_parts/Markdown.tsx +472 -0
- package/views/_parts/SchemaForm.tsx +742 -0
- package/views/_parts/fileRef.ts +21 -0
- package/views/_parts/useNarrow.ts +24 -0
- package/views/host.ts +23 -0
- package/views/human.ts +91 -0
- package/views/index.ts +16 -0
- package/views/protocol.ts +40 -0
- package/views/runtime.tsx +132 -0
- package/views/slack.test.ts +40 -0
- package/{slack-view.ts → views/slack.ts} +3 -3
- /package/dist/{slack-view.js → views/slack.js} +0 -0
|
@@ -145,6 +145,14 @@ describe("aiManifestSchema", () => {
|
|
|
145
145
|
expect(parsed.inputs.instruction?.resume).toBe(true)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
+
it("inputs の flexible(推奨型を維持して任意の型を受ける)が読める", () => {
|
|
149
|
+
const parsed = aiManifestSchema.parse({
|
|
150
|
+
...valid,
|
|
151
|
+
inputs: { in: { schema: { type: "object" }, flexible: true } },
|
|
152
|
+
})
|
|
153
|
+
expect(parsed.inputs.in?.flexible).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
|
|
148
156
|
it("旧形式(値が素の JSON Schema)は { schema: 値 } として読める", () => {
|
|
149
157
|
const legacy = aiManifestSchema.parse({
|
|
150
158
|
...valid,
|
package/_schemas/ai-manifest.ts
CHANGED
|
@@ -72,6 +72,8 @@ export const agentOfModel = (model: AiModel): AiAgentId =>
|
|
|
72
72
|
export const aiInputDeclSchema = z.object({
|
|
73
73
|
schema: jsonSchemaSchema,
|
|
74
74
|
resume: z.boolean().optional(),
|
|
75
|
+
// 推奨型を維持して任意の型を受ける(接続で上書きされず、適合チェックもしない)
|
|
76
|
+
flexible: z.boolean().optional(),
|
|
75
77
|
})
|
|
76
78
|
export type AiInputDecl = z.infer<typeof aiInputDeclSchema>
|
|
77
79
|
|
package/_schemas/graph.test.ts
CHANGED
|
@@ -174,20 +174,26 @@ describe("humanConfigSchema", () => {
|
|
|
174
174
|
).toThrow(/担当/)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
it("承認ビューを受け付ける", () => {
|
|
177
|
+
// 実装は nodes/human/ 直下のディレクトリ名(program_dir / ai_dir と同じ)。repo にあるかは保存 API が見る
|
|
178
|
+
it("実装(human_dir・ref)を受け付ける", () => {
|
|
180
179
|
const parsed = humanConfigSchema.parse({
|
|
181
180
|
assignment: { units: [[uuid()]] },
|
|
182
|
-
|
|
181
|
+
human_dir: "invoice-review",
|
|
182
|
+
ref: "try-view",
|
|
183
183
|
})
|
|
184
|
-
expect(parsed.
|
|
184
|
+
expect(parsed.human_dir).toBe("invoice-review")
|
|
185
|
+
expect(parsed.ref).toBe("try-view")
|
|
185
186
|
})
|
|
186
187
|
|
|
187
|
-
it("
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
it("空・パスの human_dir は拒否する", () => {
|
|
189
|
+
for (const human_dir of ["", "a/b", "..", "a\\b"]) {
|
|
190
|
+
expect(() =>
|
|
191
|
+
humanConfigSchema.parse({
|
|
192
|
+
assignment: { units: [[uuid()]] },
|
|
193
|
+
human_dir,
|
|
194
|
+
}),
|
|
195
|
+
).toThrow()
|
|
196
|
+
}
|
|
191
197
|
})
|
|
192
198
|
|
|
193
199
|
it("fixed:1ユニット(複数人の and/or)を受け付ける", () => {
|
|
@@ -1203,6 +1209,42 @@ describe("saveGraphBodySchema:コンポーネントノード(component kind
|
|
|
1203
1209
|
).not.toThrow()
|
|
1204
1210
|
})
|
|
1205
1211
|
|
|
1212
|
+
it("flexible な in(契約で焼き込まれた component.in・AI の宣言 in)への接続は型が違っても保存できる", () => {
|
|
1213
|
+
const prog = {
|
|
1214
|
+
...makeNode([
|
|
1215
|
+
port("in", "in"),
|
|
1216
|
+
{ ...port("out", "main"), schema: { type: "string" } },
|
|
1217
|
+
]),
|
|
1218
|
+
kind: "program" as const,
|
|
1219
|
+
}
|
|
1220
|
+
const comp = {
|
|
1221
|
+
...componentNode({ type: "number" }),
|
|
1222
|
+
}
|
|
1223
|
+
comp.ports = comp.ports.map((p) =>
|
|
1224
|
+
p.io === "in" ? { ...p, flexible: true } : p,
|
|
1225
|
+
)
|
|
1226
|
+
expect(() =>
|
|
1227
|
+
saveGraphBodySchema.parse({
|
|
1228
|
+
nodes: [prog, comp],
|
|
1229
|
+
connections: [connect(prog, comp)],
|
|
1230
|
+
}),
|
|
1231
|
+
).not.toThrow()
|
|
1232
|
+
// AI の flexible な in:鏡映されず推奨型 {type:"number"} が残るが、型が違う接続を通す
|
|
1233
|
+
const ai = {
|
|
1234
|
+
...makeNode([
|
|
1235
|
+
{ ...port("in", "in"), schema: { type: "number" }, flexible: true },
|
|
1236
|
+
port("out", "main"),
|
|
1237
|
+
]),
|
|
1238
|
+
kind: "ai" as const,
|
|
1239
|
+
}
|
|
1240
|
+
expect(() =>
|
|
1241
|
+
saveGraphBodySchema.parse({
|
|
1242
|
+
nodes: [prog, ai],
|
|
1243
|
+
connections: [connect(prog, ai)],
|
|
1244
|
+
}),
|
|
1245
|
+
).not.toThrow()
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1206
1248
|
it("component の in への接続は契約への適合を検証する(伝播型 ⊆ 契約)", () => {
|
|
1207
1249
|
// 上流 program の out {type:"string"} → component の in {type:"number"} は不適合
|
|
1208
1250
|
const prog = {
|
package/_schemas/graph.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
+
import { humanDeclarationSchema } from "./human-manifest.js"
|
|
2
3
|
import {
|
|
3
4
|
graphPortSchema,
|
|
4
5
|
jsonSchemaSchema,
|
|
@@ -6,8 +7,8 @@ import {
|
|
|
6
7
|
portKeySchema,
|
|
7
8
|
} from "./node.js"
|
|
8
9
|
import {
|
|
10
|
+
connectionAccepted,
|
|
9
11
|
hasInboundConflict,
|
|
10
|
-
isSchemaSubset,
|
|
11
12
|
kindPortsViolation,
|
|
12
13
|
resolvePortSchemas,
|
|
13
14
|
UNDECLARED_SCHEMA,
|
|
@@ -156,10 +157,23 @@ export type AssignMember = z.infer<typeof assignMemberSchema>
|
|
|
156
157
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
157
158
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
158
159
|
export const humanConfigSchema = z.object({
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
160
|
+
// 実装(承認画面と入出力の宣言)の置き場所:実行 repo の nodes/human/ 直下のディレクトリ名。
|
|
161
|
+
// program_dir / ai_dir と同じ。省略時は mawaru 内蔵の標準の承認画面
|
|
162
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「human ノードの実装として置く」)
|
|
163
|
+
human_dir: z
|
|
164
|
+
.string()
|
|
165
|
+
.min(1)
|
|
166
|
+
.refine(
|
|
167
|
+
(v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..",
|
|
168
|
+
"human_dir は nodes/human/ 直下のディレクトリ名で指定してください",
|
|
169
|
+
)
|
|
170
|
+
.optional(),
|
|
171
|
+
// 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
|
|
172
|
+
ref: z.string().min(1).optional(),
|
|
173
|
+
// 宣言の写し(in / approve / reject の型)。伝播はこれを読む。human_dir を選んだときに
|
|
174
|
+
// 一覧 API(GET /tenants/:id/github/humans)の宣言を入れる。省略すると保存時に mawaru が repo の
|
|
175
|
+
// config.json から読む(下書きに同じ human_dir の同じノードがあればその写しを保つ)
|
|
176
|
+
declaration: humanDeclarationSchema.optional(),
|
|
163
177
|
assignment: z
|
|
164
178
|
.object({
|
|
165
179
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -293,8 +307,8 @@ export const graphNodeSchema = z.object({
|
|
|
293
307
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
294
308
|
icon: nodeIconSchema.optional(),
|
|
295
309
|
// Slack ビュー(全 kind 共通の任意設定。実行中(human は承認待ち)・完了メッセージの見せ方)。
|
|
296
|
-
// human の view と同じ「不透明なキー」で、選べる key は GET slack
|
|
297
|
-
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/slack
|
|
310
|
+
// human の view と同じ「不透明なキー」で、選べる key は GET views/slack が返す。
|
|
311
|
+
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/views/slack の契約で書く)。
|
|
298
312
|
// 保存時に mawaru が見るのは common/* の実在と <slug>/* の可視性だけ。
|
|
299
313
|
// 省略=承認待ちは承認ボタンだけ・完了は ✅ の1行(データは出さない)。
|
|
300
314
|
// ポートの型には一切影響しない
|
|
@@ -551,9 +565,10 @@ export const saveGraphBodySchema = z
|
|
|
551
565
|
// 接続鏡映する口(program / ai の in)とコピー宣言のビューは接続元の型を採用する
|
|
552
566
|
// ので自明に通り、実質の検証対象は宣言で固定された口
|
|
553
567
|
// (component の契約・宣言のあるビューの human.in)になる
|
|
568
|
+
// flexible な in(AI の宣言 in・契約で焼き込まれた component の in)は型を問わない
|
|
554
569
|
const fromSchema = resolved.get(conn.from_port_id) ?? from.schema
|
|
555
570
|
const toSchema = resolved.get(conn.to_port_id) ?? to.schema
|
|
556
|
-
if (!
|
|
571
|
+
if (!connectionAccepted(fromSchema, { ...to, schema: toSchema })) {
|
|
557
572
|
ctx.addIssue({
|
|
558
573
|
code: "custom",
|
|
559
574
|
path: ["connections", i, "to_port_id"],
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
humanDeclarationOfManifest,
|
|
4
|
+
humanDeclarationSchema,
|
|
5
|
+
humanManifestSchema,
|
|
6
|
+
STANDARD_HUMAN_DECLARATION,
|
|
7
|
+
} from "./human-manifest.js"
|
|
8
|
+
import { fileSchema, instructionSchema } from "./port-spec.js"
|
|
9
|
+
|
|
10
|
+
const pr = {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
title: { type: "string", description: "PR のタイトル" },
|
|
14
|
+
gifs: { type: "array", items: fileSchema() },
|
|
15
|
+
count: { type: "integer" },
|
|
16
|
+
},
|
|
17
|
+
required: ["title"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const issuesOf = (value: unknown) => {
|
|
21
|
+
const parsed = humanDeclarationSchema.safeParse(value)
|
|
22
|
+
return parsed.success
|
|
23
|
+
? []
|
|
24
|
+
: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("humanDeclarationSchema(宣言の検査)", () => {
|
|
28
|
+
it("標準ビューの宣言と具体型の宣言を通す", () => {
|
|
29
|
+
expect(issuesOf(STANDARD_HUMAN_DECLARATION)).toEqual([])
|
|
30
|
+
expect(
|
|
31
|
+
issuesOf({ in: pr, approve: pr, reject: instructionSchema() }),
|
|
32
|
+
).toEqual([])
|
|
33
|
+
// 未宣言({})も宣言の1つ(スキーマ未定義)
|
|
34
|
+
expect(issuesOf({ in: {}, approve: "T", reject: {} })).toEqual([])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("type の配列(nullable)は場所つきで弾く", () => {
|
|
38
|
+
expect(
|
|
39
|
+
issuesOf({
|
|
40
|
+
in: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: { summary: { type: ["string", "null"] } },
|
|
43
|
+
},
|
|
44
|
+
approve: "T",
|
|
45
|
+
reject: instructionSchema(),
|
|
46
|
+
}),
|
|
47
|
+
).toEqual([
|
|
48
|
+
"in.properties.summary.type: 配列(nullable)は使えません。任意の値は required から外してください",
|
|
49
|
+
])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("知らない type・形の崩れた properties / required / items を弾く", () => {
|
|
53
|
+
expect(
|
|
54
|
+
issuesOf({
|
|
55
|
+
in: { type: "strin" },
|
|
56
|
+
approve: { type: "object", properties: [] },
|
|
57
|
+
reject: {
|
|
58
|
+
type: "object",
|
|
59
|
+
required: [1],
|
|
60
|
+
properties: { list: { type: "array", items: "x" } },
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
).toEqual([
|
|
64
|
+
"in.type: type は object / array / string / number / integer / boolean のどれかにしてください(strin)",
|
|
65
|
+
"approve.properties: properties はオブジェクトで書いてください",
|
|
66
|
+
"reject.properties.list.items: items はスキーマ(オブジェクト)で書いてください",
|
|
67
|
+
"reject.required: required は文字列の配列で書いてください",
|
|
68
|
+
])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("type の無い構造(properties / items だけ)を弾く", () => {
|
|
72
|
+
expect(
|
|
73
|
+
issuesOf({
|
|
74
|
+
in: { properties: { a: { type: "string" } } },
|
|
75
|
+
approve: "T",
|
|
76
|
+
reject: instructionSchema(),
|
|
77
|
+
}),
|
|
78
|
+
).toEqual([
|
|
79
|
+
"in.type: properties / items を書くときは type も書いてください",
|
|
80
|
+
])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('reject は上流のコピー("T")にできない', () => {
|
|
84
|
+
expect(issuesOf({ in: "T", approve: "T", reject: "T" })).not.toEqual([])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("humanManifestSchema(nodes/human/<dir>/config.json)", () => {
|
|
89
|
+
it("省略した口は上流のコピー(in)・in と同型(approve)・標準の差し戻し(reject)になる", () => {
|
|
90
|
+
const config = humanManifestSchema.parse({})
|
|
91
|
+
expect(humanDeclarationOfManifest(config)).toEqual(
|
|
92
|
+
STANDARD_HUMAN_DECLARATION,
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("書いた口の型をそのまま宣言にする", () => {
|
|
97
|
+
const config = humanManifestSchema.parse({
|
|
98
|
+
inputs: { in: { schema: pr } },
|
|
99
|
+
outputs: { approve: { type: "array", items: pr }, reject: pr },
|
|
100
|
+
})
|
|
101
|
+
expect(humanDeclarationOfManifest(config)).toEqual({
|
|
102
|
+
in: pr,
|
|
103
|
+
approve: { type: "array", items: pr },
|
|
104
|
+
reject: pr,
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("in / approve / reject 以外の口・知らないキーは弾く", () => {
|
|
109
|
+
expect(
|
|
110
|
+
humanManifestSchema.safeParse({ inputs: { instruction: {} } }).success,
|
|
111
|
+
).toBe(false)
|
|
112
|
+
expect(
|
|
113
|
+
humanManifestSchema.safeParse({ outputs: { notion: {} } }).success,
|
|
114
|
+
).toBe(false)
|
|
115
|
+
expect(humanManifestSchema.safeParse({ name: "x" }).success).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("中の型も宣言の検査を通す", () => {
|
|
119
|
+
const parsed = humanManifestSchema.safeParse({
|
|
120
|
+
inputs: { in: { schema: { type: ["string", "null"] } } },
|
|
121
|
+
})
|
|
122
|
+
expect(parsed.success).toBe(false)
|
|
123
|
+
expect(
|
|
124
|
+
parsed.error?.issues.map((i) => `${i.path.join(".")}: ${i.message}`),
|
|
125
|
+
).toEqual([
|
|
126
|
+
"inputs.in.schema.type: 配列(nullable)は使えません。任意の値は required から外してください",
|
|
127
|
+
])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { type JsonSchema, jsonSchemaSchema } from "./node.js"
|
|
3
|
+
import {
|
|
4
|
+
COPY_UPSTREAM_SCHEMA,
|
|
5
|
+
type DeclaredPortSchema,
|
|
6
|
+
instructionSchema,
|
|
7
|
+
} from "./port-spec.js"
|
|
8
|
+
|
|
9
|
+
// ---- human ノードの宣言(in / approve / reject の型) ----
|
|
10
|
+
// 正は実装を持つ側(顧客 repo の nodes/human/<dir>/config.json。実装を指さないノードは標準の宣言)。
|
|
11
|
+
// human ノードは実装を選んだ時点の写し(node_humans.declaration)を持ち、伝播はそれを読む
|
|
12
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「宣言(ポート型)」)。
|
|
13
|
+
// "T"(COPY_UPSTREAM_SCHEMA)は「上流の out をそのまま受ける」(in)/「in と同型」(approve)。
|
|
14
|
+
// reject は具体型だけ(上流のコピーは意味を持たない)
|
|
15
|
+
|
|
16
|
+
export type HumanDeclaration = {
|
|
17
|
+
in: DeclaredPortSchema
|
|
18
|
+
approve: DeclaredPortSchema
|
|
19
|
+
reject: JsonSchema
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 宣言に書ける type。file は x-mawaru-type マーカー付きの object
|
|
23
|
+
const DECLARED_TYPES = [
|
|
24
|
+
"object",
|
|
25
|
+
"array",
|
|
26
|
+
"string",
|
|
27
|
+
"number",
|
|
28
|
+
"integer",
|
|
29
|
+
"boolean",
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
33
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
34
|
+
|
|
35
|
+
// JSON Schema の形の崩れを場所つきで返す。接続の比較(schemaFit)が扱える形だけを通す:
|
|
36
|
+
// 顧客 repo の宣言は mawaru のレビューを通らないので、壊れた型がノードに写る前に止める
|
|
37
|
+
const schemaIssues = (
|
|
38
|
+
schema: Record<string, unknown>,
|
|
39
|
+
path: (string | number)[],
|
|
40
|
+
): { path: (string | number)[]; message: string }[] => {
|
|
41
|
+
const issues: { path: (string | number)[]; message: string }[] = []
|
|
42
|
+
const { type, properties, required, items } = schema
|
|
43
|
+
if (Array.isArray(type)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
path: [...path, "type"],
|
|
46
|
+
message:
|
|
47
|
+
"配列(nullable)は使えません。任意の値は required から外してください",
|
|
48
|
+
})
|
|
49
|
+
} else if (
|
|
50
|
+
type !== undefined &&
|
|
51
|
+
!DECLARED_TYPES.includes(type as (typeof DECLARED_TYPES)[number])
|
|
52
|
+
) {
|
|
53
|
+
issues.push({
|
|
54
|
+
path: [...path, "type"],
|
|
55
|
+
message: `type は ${DECLARED_TYPES.join(" / ")} のどれかにしてください(${String(type)})`,
|
|
56
|
+
})
|
|
57
|
+
} else if (
|
|
58
|
+
type === undefined &&
|
|
59
|
+
(properties !== undefined || items !== undefined)
|
|
60
|
+
) {
|
|
61
|
+
issues.push({
|
|
62
|
+
path: [...path, "type"],
|
|
63
|
+
message: "properties / items を書くときは type も書いてください",
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
if (properties !== undefined) {
|
|
67
|
+
if (!isPlainObject(properties)) {
|
|
68
|
+
issues.push({
|
|
69
|
+
path: [...path, "properties"],
|
|
70
|
+
message: "properties はオブジェクトで書いてください",
|
|
71
|
+
})
|
|
72
|
+
} else {
|
|
73
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
74
|
+
if (!isPlainObject(child)) {
|
|
75
|
+
issues.push({
|
|
76
|
+
path: [...path, "properties", key],
|
|
77
|
+
message: "スキーマはオブジェクトで書いてください",
|
|
78
|
+
})
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
issues.push(...schemaIssues(child, [...path, "properties", key]))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
required !== undefined &&
|
|
87
|
+
!(Array.isArray(required) && required.every((r) => typeof r === "string"))
|
|
88
|
+
) {
|
|
89
|
+
issues.push({
|
|
90
|
+
path: [...path, "required"],
|
|
91
|
+
message: "required は文字列の配列で書いてください",
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
if (items !== undefined) {
|
|
95
|
+
if (!isPlainObject(items)) {
|
|
96
|
+
issues.push({
|
|
97
|
+
path: [...path, "items"],
|
|
98
|
+
message: "items はスキーマ(オブジェクト)で書いてください",
|
|
99
|
+
})
|
|
100
|
+
} else {
|
|
101
|
+
issues.push(...schemaIssues(items, [...path, "items"]))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return issues
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 宣言に書く1つの型(検査つき)
|
|
108
|
+
const checkedSchema = jsonSchemaSchema.superRefine((schema, ctx) => {
|
|
109
|
+
for (const issue of schemaIssues(schema, []))
|
|
110
|
+
ctx.addIssue({ code: "custom", ...issue })
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const declaredPortSchema = z.union([
|
|
114
|
+
z.literal(COPY_UPSTREAM_SCHEMA),
|
|
115
|
+
checkedSchema,
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
export const humanDeclarationSchema = z.object({
|
|
119
|
+
in: declaredPortSchema,
|
|
120
|
+
approve: declaredPortSchema,
|
|
121
|
+
reject: checkedSchema,
|
|
122
|
+
}) satisfies z.ZodType<HumanDeclaration>
|
|
123
|
+
|
|
124
|
+
// 標準ビュー(common/standard)の宣言:上流の型をそのまま受けて出し、差し戻しは標準の指示フォーム
|
|
125
|
+
export const STANDARD_HUMAN_DECLARATION: HumanDeclaration = {
|
|
126
|
+
in: COPY_UPSTREAM_SCHEMA,
|
|
127
|
+
approve: COPY_UPSTREAM_SCHEMA,
|
|
128
|
+
reject: instructionSchema(),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---- 顧客 repo の nodes/human/<dir>/config.json ----
|
|
132
|
+
// 形は AI の config.json に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
|
|
133
|
+
// 口は in / approve / reject 固定。省略した口は STANDARD_HUMAN_DECLARATION と同じ扱い
|
|
134
|
+
export const humanManifestSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
inputs: z
|
|
137
|
+
.object({ in: z.object({ schema: checkedSchema }).strict().optional() })
|
|
138
|
+
.strict()
|
|
139
|
+
.optional(),
|
|
140
|
+
outputs: z
|
|
141
|
+
.object({
|
|
142
|
+
approve: checkedSchema.optional(),
|
|
143
|
+
reject: checkedSchema.optional(),
|
|
144
|
+
})
|
|
145
|
+
.strict()
|
|
146
|
+
.optional(),
|
|
147
|
+
})
|
|
148
|
+
.strict()
|
|
149
|
+
|
|
150
|
+
export type HumanManifest = z.infer<typeof humanManifestSchema>
|
|
151
|
+
|
|
152
|
+
export const humanDeclarationOfManifest = (
|
|
153
|
+
config: HumanManifest,
|
|
154
|
+
): HumanDeclaration => ({
|
|
155
|
+
in: config.inputs?.in?.schema ?? COPY_UPSTREAM_SCHEMA,
|
|
156
|
+
approve: config.outputs?.approve ?? COPY_UPSTREAM_SCHEMA,
|
|
157
|
+
reject: config.outputs?.reject ?? instructionSchema(),
|
|
158
|
+
})
|
package/_schemas/node.ts
CHANGED
|
@@ -54,6 +54,10 @@ export const graphPortSchema = z
|
|
|
54
54
|
// 再開する(AI ノードで意味を持つ。key ではなくこの属性が再開を決める。
|
|
55
55
|
// ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
56
56
|
resume: z.boolean().optional(),
|
|
57
|
+
// in ポートの型属性:true なら宣言(推奨型)を維持し、接続元の型による上書き・
|
|
58
|
+
// 適合チェックをしない(AI の in で設定。start / component へは導出で流れる。
|
|
59
|
+
// AIノード入力のflexible属性(任意の型で呼べるループ).md)
|
|
60
|
+
flexible: z.boolean().optional(),
|
|
57
61
|
side: portSideSchema.optional(),
|
|
58
62
|
offset: z.number().min(0).max(1).optional(),
|
|
59
63
|
})
|