@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.
Files changed (91) hide show
  1. package/_schemas/ai-manifest.test.ts +8 -0
  2. package/_schemas/ai-manifest.ts +2 -0
  3. package/_schemas/graph.test.ts +51 -9
  4. package/_schemas/graph.ts +23 -8
  5. package/_schemas/human-manifest.test.ts +129 -0
  6. package/_schemas/human-manifest.ts +158 -0
  7. package/_schemas/node.ts +4 -0
  8. package/_schemas/port-spec.test.ts +164 -40
  9. package/_schemas/port-spec.ts +77 -26
  10. package/dist/_cli/login.js +1 -15
  11. package/dist/_cli/openBrowser.d.ts +1 -0
  12. package/dist/_cli/openBrowser.js +15 -0
  13. package/dist/_cli/previewApi.d.ts +1 -1
  14. package/dist/_cli/previewCommand.d.ts +8 -0
  15. package/dist/_cli/previewCommand.js +95 -37
  16. package/dist/_cli/previewFiles.d.ts +2 -1
  17. package/dist/_cli/previewFiles.js +42 -19
  18. package/dist/_cli/previewSlack.d.ts +1 -1
  19. package/dist/_cli/previewSlack.js +1 -1
  20. package/dist/_schemas/ai-manifest.d.ts +3 -0
  21. package/dist/_schemas/ai-manifest.js +2 -0
  22. package/dist/_schemas/graph.d.ts +23 -3
  23. package/dist/_schemas/graph.js +20 -8
  24. package/dist/_schemas/human-manifest.d.ts +27 -0
  25. package/dist/_schemas/human-manifest.js +121 -0
  26. package/dist/_schemas/node.d.ts +1 -0
  27. package/dist/_schemas/node.js +4 -0
  28. package/dist/_schemas/port-spec.d.ts +8 -9
  29. package/dist/_schemas/port-spec.js +64 -9
  30. package/dist/cli.js +3 -2
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +1 -0
  33. package/dist/init.js +22 -3
  34. package/dist/typegen.d.ts +3 -1
  35. package/dist/typegen.js +96 -14
  36. package/dist/validate.js +55 -0
  37. package/dist/views/_approval/ApprovalBar.d.ts +20 -0
  38. package/dist/views/_approval/ApprovalBar.js +74 -0
  39. package/dist/views/_approvalActions.d.ts +18 -0
  40. package/dist/views/_approvalActions.js +10 -0
  41. package/dist/views/_bridge.d.ts +12 -0
  42. package/dist/views/_bridge.js +10 -0
  43. package/dist/views/_parts/DocumentCard.d.ts +2 -0
  44. package/dist/views/_parts/DocumentCard.js +16 -0
  45. package/dist/views/_parts/FilePreview.d.ts +5 -0
  46. package/dist/views/_parts/FilePreview.js +101 -0
  47. package/dist/views/_parts/Linkify.d.ts +7 -0
  48. package/dist/views/_parts/Linkify.js +28 -0
  49. package/dist/views/_parts/Markdown.d.ts +44 -0
  50. package/dist/views/_parts/Markdown.js +313 -0
  51. package/dist/views/_parts/SchemaForm.d.ts +19 -0
  52. package/dist/views/_parts/SchemaForm.js +310 -0
  53. package/dist/views/_parts/fileRef.d.ts +7 -0
  54. package/dist/views/_parts/fileRef.js +11 -0
  55. package/dist/views/_parts/useNarrow.d.ts +5 -0
  56. package/dist/views/_parts/useNarrow.js +23 -0
  57. package/dist/views/host.d.ts +18 -0
  58. package/dist/views/host.js +20 -0
  59. package/dist/views/human.d.ts +73 -0
  60. package/dist/views/human.js +6 -0
  61. package/dist/views/index.d.ts +8 -0
  62. package/dist/views/index.js +12 -0
  63. package/dist/views/protocol.d.ts +34 -0
  64. package/dist/views/protocol.js +5 -0
  65. package/dist/views/runtime.d.ts +3 -0
  66. package/dist/views/runtime.js +86 -0
  67. package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
  68. package/docs/development.md +45 -5
  69. package/index.ts +1 -0
  70. package/package.json +36 -4
  71. package/skills/create-loop/SKILL.md +6 -3
  72. package/views/_approval/ApprovalBar.tsx +239 -0
  73. package/views/_approvalActions.tsx +31 -0
  74. package/views/_bridge.tsx +36 -0
  75. package/views/_parts/DocumentCard.ts +18 -0
  76. package/views/_parts/FilePreview.tsx +180 -0
  77. package/views/_parts/Linkify.test.ts +40 -0
  78. package/views/_parts/Linkify.tsx +40 -0
  79. package/views/_parts/Markdown.test.ts +131 -0
  80. package/views/_parts/Markdown.tsx +472 -0
  81. package/views/_parts/SchemaForm.tsx +742 -0
  82. package/views/_parts/fileRef.ts +21 -0
  83. package/views/_parts/useNarrow.ts +24 -0
  84. package/views/host.ts +23 -0
  85. package/views/human.ts +91 -0
  86. package/views/index.ts +16 -0
  87. package/views/protocol.ts +40 -0
  88. package/views/runtime.tsx +132 -0
  89. package/views/slack.test.ts +40 -0
  90. package/{slack-view.ts → views/slack.ts} +3 -3
  91. /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,
@@ -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
 
@@ -174,20 +174,26 @@ describe("humanConfigSchema", () => {
174
174
  ).toThrow(/担当/)
175
175
  })
176
176
 
177
- // 承認ビューは不透明なキー(program_dir / ai_dir と同じ)。実在するか・そのテナントが
178
- // 選べるかは mawaru 側のカタログ(非公開)と保存 API が見るので、ここは形だけ
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
- view: "acme/invoice",
181
+ human_dir: "invoice-review",
182
+ ref: "try-view",
183
183
  })
184
- expect(parsed.view).toBe("acme/invoice")
184
+ expect(parsed.human_dir).toBe("invoice-review")
185
+ expect(parsed.ref).toBe("try-view")
185
186
  })
186
187
 
187
- it("空のビューキーは拒否する", () => {
188
- expect(() =>
189
- humanConfigSchema.parse({ assignment: { units: [[uuid()]] }, view: "" }),
190
- ).toThrow()
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
- // 承認 UI(承認ビュー)の識別子。program_dir / ai_dir と同じ「不透明なキー」で、
160
- // どのキーが実在するか・そのテナントが選べるかは保存時に mawaru 側が検証する
161
- // (カタログは非公開。@mawaru/common の approval-view.ts)。省略時は標準ビュー
162
- view: z.string().min(1).optional(),
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-views が返す。
297
- // <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/slack-view の契約で書く)。
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 (!isSchemaSubset(fromSchema, toSchema)) {
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
  })