@mawaru/sdk 0.11.0 → 0.14.1
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 +54 -59
- package/_schemas/ai-manifest.ts +38 -23
- package/_schemas/duplicate-selection.test.ts +235 -0
- package/_schemas/duplicate-selection.ts +48 -0
- package/_schemas/extract-component.test.ts +51 -2
- package/_schemas/extract-component.ts +4 -2
- package/_schemas/graph.test.ts +105 -86
- package/_schemas/graph.ts +20 -77
- package/_schemas/node.ts +4 -0
- package/_schemas/port-spec.test.ts +194 -113
- package/_schemas/port-spec.ts +95 -107
- package/dist/_cli/credentials.d.ts +14 -0
- package/dist/_cli/credentials.js +81 -0
- package/dist/_cli/login.d.ts +5 -0
- package/dist/_cli/login.js +82 -0
- package/dist/_cli/resumeSession.d.ts +21 -0
- package/dist/_cli/resumeSession.js +125 -0
- package/dist/_cli/sessionCommand.d.ts +3 -0
- package/dist/_cli/sessionCommand.js +55 -0
- package/dist/_schemas/ai-manifest.d.ts +12 -1
- package/dist/_schemas/ai-manifest.js +35 -20
- package/dist/_schemas/duplicate-selection.d.ts +12 -0
- package/dist/_schemas/duplicate-selection.js +34 -0
- package/dist/_schemas/extract-component.js +4 -2
- package/dist/_schemas/graph.d.ts +14 -12
- package/dist/_schemas/graph.js +21 -60
- package/dist/_schemas/hook-manifest.d.ts +2 -2
- package/dist/_schemas/node.d.ts +3 -2
- package/dist/_schemas/node.js +4 -0
- package/dist/_schemas/port-spec.d.ts +11 -6
- package/dist/_schemas/port-spec.js +72 -97
- package/dist/cli.js +50 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +1 -1
- package/dist/typegen.js +6 -6
- package/docs/development.md +5 -1
- package/index.ts +1 -0
- package/package.json +11 -18
- package/skills/create-loop/SKILL.md +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/echo/main.ts +1 -1
- package/templates/mawaru-runner.yml +1 -1
package/_schemas/graph.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
waitConfigSchema,
|
|
9
9
|
} from "./graph.js"
|
|
10
10
|
import type { GraphPort } from "./node.js"
|
|
11
|
-
import {
|
|
11
|
+
import { resolvePortSchemas } from "./port-spec.js"
|
|
12
12
|
|
|
13
13
|
// テスト用の決定的な uuid(common は node 型定義を持たないため crypto は使わない)
|
|
14
14
|
let seq = 0
|
|
@@ -89,15 +89,14 @@ const port = (io: "in" | "out", key: string): GraphPort => ({
|
|
|
89
89
|
schema: { type: "object" },
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
// ai の out
|
|
93
|
-
|
|
94
|
-
const textEnvelope = envelopeSchemaOf({ type: "string" })
|
|
92
|
+
// ai の out も自由な宣言型。デフォルトノードはテキスト(string)を宣言する
|
|
93
|
+
const textOutput = { type: "string" }
|
|
95
94
|
|
|
96
95
|
// 既定は ai ノード。ai の in は 上流データの in + 差し戻しの instruction の2口
|
|
97
96
|
const aiPorts = (): GraphPort[] => [
|
|
98
97
|
port("in", "in"),
|
|
99
98
|
port("in", "instruction"),
|
|
100
|
-
{ ...port("out", "main"), schema:
|
|
99
|
+
{ ...port("out", "main"), schema: textOutput },
|
|
101
100
|
]
|
|
102
101
|
|
|
103
102
|
// ai 以外に流用するとき用(in は1つ)
|
|
@@ -153,8 +152,8 @@ describe("programConfigSchema", () => {
|
|
|
153
152
|
|
|
154
153
|
describe("aiConfigSchema", () => {
|
|
155
154
|
it("ai_dir を受け付ける(ref は省略可・日本語可)", () => {
|
|
156
|
-
expect(aiConfigSchema.parse({ ai_dir: "
|
|
157
|
-
ai_dir: "
|
|
155
|
+
expect(aiConfigSchema.parse({ ai_dir: "replyComposer" })).toEqual({
|
|
156
|
+
ai_dir: "replyComposer",
|
|
158
157
|
})
|
|
159
158
|
expect(
|
|
160
159
|
aiConfigSchema.parse({ ai_dir: "返信下書き", ref: "develop" }).ref,
|
|
@@ -181,14 +180,12 @@ describe("humanConfigSchema", () => {
|
|
|
181
180
|
|
|
182
181
|
// 承認ビューは不透明なキー(program_dir / ai_dir と同じ)。実在するか・そのテナントが
|
|
183
182
|
// 選べるかは mawaru 側のカタログ(非公開)と保存 API が見るので、ここは形だけ
|
|
184
|
-
it("
|
|
183
|
+
it("承認ビューを受け付ける", () => {
|
|
185
184
|
const parsed = humanConfigSchema.parse({
|
|
186
185
|
assignment: {},
|
|
187
186
|
view: "acme/invoice",
|
|
188
|
-
view_config: { threshold: 1000 },
|
|
189
187
|
})
|
|
190
188
|
expect(parsed.view).toBe("acme/invoice")
|
|
191
|
-
expect(parsed.view_config).toEqual({ threshold: 1000 })
|
|
192
189
|
})
|
|
193
190
|
|
|
194
191
|
it("空のビューキーは拒否する", () => {
|
|
@@ -390,17 +387,27 @@ describe("saveGraphBodySchema", () => {
|
|
|
390
387
|
).toThrow(/key が重複/)
|
|
391
388
|
})
|
|
392
389
|
|
|
393
|
-
it("in
|
|
390
|
+
it("in ポートの口数:ai は1つ以上(自由)、program 等は ちょうど1つ", () => {
|
|
391
|
+
// ai:0個は拒否、複数は許可(in の追加・削除は自由)
|
|
394
392
|
const zero = makeNode([port("out", "main")])
|
|
395
393
|
expect(() =>
|
|
396
394
|
saveGraphBodySchema.parse({ nodes: [zero], connections: [] }),
|
|
397
395
|
).toThrow(/in ポート/)
|
|
398
|
-
|
|
399
|
-
const two = makeNode([
|
|
396
|
+
const many = makeNode([
|
|
400
397
|
port("in", "in"),
|
|
401
|
-
port("in", "
|
|
398
|
+
port("in", "feedback"),
|
|
399
|
+
port("in", "extra"),
|
|
402
400
|
port("out", "main"),
|
|
403
401
|
])
|
|
402
|
+
expect(() =>
|
|
403
|
+
saveGraphBodySchema.parse({ nodes: [many], connections: [] }),
|
|
404
|
+
).not.toThrow()
|
|
405
|
+
|
|
406
|
+
// program:ちょうど1つ
|
|
407
|
+
const two = {
|
|
408
|
+
...makeNode([port("in", "in"), port("in", "in2"), port("out", "main")]),
|
|
409
|
+
kind: "program" as const,
|
|
410
|
+
}
|
|
404
411
|
expect(() =>
|
|
405
412
|
saveGraphBodySchema.parse({ nodes: [two], connections: [] }),
|
|
406
413
|
).toThrow(/in ポート/)
|
|
@@ -499,8 +506,6 @@ describe("saveGraphBodySchema", () => {
|
|
|
499
506
|
|
|
500
507
|
// ---- kind 別ポート構成と型の接続バリデーション(ポートのkind別仕様.md) ----
|
|
501
508
|
|
|
502
|
-
const aiEnvelope = textEnvelope
|
|
503
|
-
|
|
504
509
|
const humanNode = () => ({
|
|
505
510
|
...makeNode([
|
|
506
511
|
port("in", "in"),
|
|
@@ -513,7 +518,7 @@ const aiNode = () => ({
|
|
|
513
518
|
...makeNode([
|
|
514
519
|
port("in", "in"),
|
|
515
520
|
port("in", "instruction"),
|
|
516
|
-
{ ...port("out", "main"), schema:
|
|
521
|
+
{ ...port("out", "main"), schema: textOutput },
|
|
517
522
|
]),
|
|
518
523
|
kind: "ai" as const,
|
|
519
524
|
})
|
|
@@ -551,27 +556,27 @@ describe("saveGraphBodySchema(kind 別ポート構成)", () => {
|
|
|
551
556
|
...makeNode([
|
|
552
557
|
port("in", "in"),
|
|
553
558
|
port("in", "instruction"),
|
|
554
|
-
{ ...port("out", "spam"), schema:
|
|
555
|
-
{ ...port("out", "sales"), schema:
|
|
556
|
-
{ ...port("out", "support"), schema:
|
|
559
|
+
{ ...port("out", "spam"), schema: textOutput },
|
|
560
|
+
{ ...port("out", "sales"), schema: textOutput },
|
|
561
|
+
{ ...port("out", "support"), schema: textOutput },
|
|
557
562
|
]),
|
|
558
563
|
kind: "ai" as const,
|
|
559
564
|
}
|
|
560
565
|
expect(() =>
|
|
561
566
|
saveGraphBodySchema.parse({ nodes: [multi], connections: [] }),
|
|
562
567
|
).not.toThrow()
|
|
563
|
-
// in
|
|
564
|
-
const
|
|
568
|
+
// in の key も自由(in / instruction の固定構成は廃止)
|
|
569
|
+
const freeIn = {
|
|
565
570
|
...makeNode([
|
|
566
571
|
port("in", "input"),
|
|
567
|
-
port("in", "
|
|
568
|
-
{ ...port("out", "main"), schema:
|
|
572
|
+
port("in", "feedback"),
|
|
573
|
+
{ ...port("out", "main"), schema: textOutput },
|
|
569
574
|
]),
|
|
570
575
|
kind: "ai" as const,
|
|
571
576
|
}
|
|
572
577
|
expect(() =>
|
|
573
|
-
saveGraphBodySchema.parse({ nodes: [
|
|
574
|
-
).toThrow(
|
|
578
|
+
saveGraphBodySchema.parse({ nodes: [freeIn], connections: [] }),
|
|
579
|
+
).not.toThrow()
|
|
575
580
|
})
|
|
576
581
|
|
|
577
582
|
it("program は out を複数持てる(key 自由)", () => {
|
|
@@ -588,51 +593,41 @@ describe("saveGraphBodySchema(kind 別ポート構成)", () => {
|
|
|
588
593
|
).not.toThrow()
|
|
589
594
|
})
|
|
590
595
|
|
|
591
|
-
it("ai
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
])
|
|
597
|
-
expect(() =>
|
|
598
|
-
saveGraphBodySchema.parse({ nodes: [bad], connections: [] }),
|
|
599
|
-
).toThrow(/封筒型 \{ data, description \}/)
|
|
600
|
-
// main 以外の出口も封筒型を強制する
|
|
601
|
-
const badExtra = makeNode([
|
|
602
|
-
port("in", "in"),
|
|
603
|
-
port("in", "instruction"),
|
|
604
|
-
{ ...port("out", "main"), schema: aiEnvelope },
|
|
605
|
-
{ ...port("out", "extra"), schema: { type: "object" } },
|
|
606
|
-
])
|
|
607
|
-
expect(() =>
|
|
608
|
-
saveGraphBodySchema.parse({ nodes: [badExtra], connections: [] }),
|
|
609
|
-
).toThrow(/封筒型 \{ data, description \}/)
|
|
610
|
-
// data は任意の型でよい(カタログ外の object も通る)
|
|
611
|
-
const custom = makeNode([
|
|
612
|
-
port("in", "in"),
|
|
613
|
-
port("in", "instruction"),
|
|
596
|
+
it("ai の出力宣言は任意の JSON Schema でよい(形の強制はしない)", () => {
|
|
597
|
+
for (const schema of [
|
|
598
|
+
{ type: "object" },
|
|
599
|
+
{ type: "string" },
|
|
600
|
+
{ type: "array", items: { type: "number" } },
|
|
614
601
|
{
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
602
|
+
// かつて強制していた { data, description } もただの object として valid
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {
|
|
605
|
+
data: { type: "string" },
|
|
606
|
+
description: { type: "string" },
|
|
607
|
+
},
|
|
608
|
+
required: ["data", "description"],
|
|
621
609
|
},
|
|
622
|
-
])
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
610
|
+
]) {
|
|
611
|
+
const node = makeNode([
|
|
612
|
+
port("in", "in"),
|
|
613
|
+
port("in", "instruction"),
|
|
614
|
+
{ ...port("out", "main"), schema },
|
|
615
|
+
])
|
|
616
|
+
expect(() =>
|
|
617
|
+
saveGraphBodySchema.parse({ nodes: [node], connections: [] }),
|
|
618
|
+
).not.toThrow()
|
|
619
|
+
}
|
|
626
620
|
})
|
|
627
621
|
})
|
|
628
622
|
|
|
629
623
|
describe("saveGraphBodySchema(型の接続バリデーション)", () => {
|
|
630
|
-
|
|
624
|
+
// 接続の可否は kind ではなく型で決まる。reject / instruction の型固定は廃止され、
|
|
625
|
+
// ai の in はどの口も接続鏡映(接続元の型に同期)なので接続は自明に通る
|
|
626
|
+
// → docs/tasks/wip/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
627
|
+
it("human の reject → ai の instruction を許可(kind の縛りも型固定も無い)", () => {
|
|
631
628
|
const human = humanNode()
|
|
632
629
|
const ai = aiNode()
|
|
633
|
-
const prog = programNode()
|
|
634
630
|
const rejectPort = outByKey(human, "reject")
|
|
635
|
-
// 差し戻しは指示の口(instruction)へ
|
|
636
631
|
expect(() =>
|
|
637
632
|
saveGraphBodySchema.parse({
|
|
638
633
|
nodes: [human, ai],
|
|
@@ -644,43 +639,43 @@ describe("saveGraphBodySchema(型の接続バリデーション)", () => {
|
|
|
644
639
|
],
|
|
645
640
|
}),
|
|
646
641
|
).not.toThrow()
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
{ from_port_id: rejectPort.id, to_port_id: inByKey(ai, "in").id },
|
|
653
|
-
],
|
|
654
|
-
}),
|
|
655
|
-
).toThrow(/instruction/)
|
|
656
|
-
// ai 以外にも繋げない
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it("reject は ai 以外へも繋げる(接続鏡映の in が接続元の型を採用する)", () => {
|
|
645
|
+
const human = humanNode()
|
|
646
|
+
const prog = programNode()
|
|
657
647
|
expect(() =>
|
|
658
648
|
saveGraphBodySchema.parse({
|
|
659
649
|
nodes: [human, prog],
|
|
660
650
|
connections: [
|
|
661
|
-
{
|
|
651
|
+
{
|
|
652
|
+
from_port_id: outByKey(human, "reject").id,
|
|
653
|
+
to_port_id: inOf(prog).id,
|
|
654
|
+
},
|
|
662
655
|
],
|
|
663
656
|
}),
|
|
664
|
-
).toThrow(
|
|
657
|
+
).not.toThrow()
|
|
665
658
|
})
|
|
666
659
|
|
|
667
|
-
it("instruction
|
|
668
|
-
const
|
|
669
|
-
const
|
|
660
|
+
it("instruction も他の in と同じ接続鏡映:宣言と違う型の out でも同期して繋がる", () => {
|
|
661
|
+
const upstream = aiNode()
|
|
662
|
+
const downstream = aiNode()
|
|
663
|
+
// ai.main は string、instruction の宣言は { type: "object" } だが、
|
|
664
|
+
// 鏡映で接続元に同期される(instruction の特別扱いは無い)
|
|
670
665
|
expect(() =>
|
|
671
666
|
saveGraphBodySchema.parse({
|
|
672
|
-
nodes: [
|
|
667
|
+
nodes: [upstream, downstream],
|
|
673
668
|
connections: [
|
|
674
669
|
{
|
|
675
|
-
from_port_id: outOf(
|
|
676
|
-
to_port_id: inByKey(
|
|
670
|
+
from_port_id: outOf(upstream).id,
|
|
671
|
+
to_port_id: inByKey(downstream, "instruction").id,
|
|
677
672
|
},
|
|
678
673
|
],
|
|
679
674
|
}),
|
|
680
|
-
).toThrow(
|
|
675
|
+
).not.toThrow()
|
|
681
676
|
})
|
|
682
677
|
|
|
683
|
-
it("human の in
|
|
678
|
+
it("human の in はどの型でも接続できる(形の制約は課さない)", () => {
|
|
684
679
|
const ai = aiNode()
|
|
685
680
|
const human = humanNode()
|
|
686
681
|
expect(() =>
|
|
@@ -690,8 +685,7 @@ describe("saveGraphBodySchema(型の接続バリデーション)", () => {
|
|
|
690
685
|
}),
|
|
691
686
|
).not.toThrow()
|
|
692
687
|
|
|
693
|
-
// program out
|
|
694
|
-
// (input 全体を data として表示する)
|
|
688
|
+
// program out が素の object でも接続できる
|
|
695
689
|
const human2 = humanNode()
|
|
696
690
|
const progPlain = {
|
|
697
691
|
...makeNode([
|
|
@@ -709,7 +703,8 @@ describe("saveGraphBodySchema(型の接続バリデーション)", () => {
|
|
|
709
703
|
})
|
|
710
704
|
|
|
711
705
|
it("program の in への接続は 伝播型 ⊆ 宣言スキーマ(human 経由の伝播込み)", () => {
|
|
712
|
-
// ai
|
|
706
|
+
// ai → human(approve は in と同型)→ program(string を要求) は、
|
|
707
|
+
// program の in が接続鏡映(sticky)なので通る
|
|
713
708
|
const ai = aiNode()
|
|
714
709
|
const human = humanNode()
|
|
715
710
|
const prog = programNode({ type: "string" })
|
|
@@ -1029,6 +1024,30 @@ describe("saveGraphBodySchema:コンポーネントノード(component kind
|
|
|
1029
1024
|
).not.toThrow()
|
|
1030
1025
|
})
|
|
1031
1026
|
|
|
1027
|
+
it("component.refs(配置の設定値)を受け付けて保持する(省略も可)", () => {
|
|
1028
|
+
const withRefs = {
|
|
1029
|
+
...componentNode(),
|
|
1030
|
+
component: {
|
|
1031
|
+
component_loop_id: uuid(),
|
|
1032
|
+
refs: { labels: ["bug"], assignee: "kazuwombat" },
|
|
1033
|
+
},
|
|
1034
|
+
}
|
|
1035
|
+
const parsed = saveGraphBodySchema.parse({
|
|
1036
|
+
nodes: [withRefs],
|
|
1037
|
+
connections: [],
|
|
1038
|
+
})
|
|
1039
|
+
expect(parsed.nodes[0]?.component?.refs).toEqual({
|
|
1040
|
+
labels: ["bug"],
|
|
1041
|
+
assignee: "kazuwombat",
|
|
1042
|
+
})
|
|
1043
|
+
// 省略時は undefined のまま(既存グラフの互換。保存側で {} 扱い)
|
|
1044
|
+
const omitted = saveGraphBodySchema.parse({
|
|
1045
|
+
nodes: [componentNode()],
|
|
1046
|
+
connections: [],
|
|
1047
|
+
})
|
|
1048
|
+
expect(omitted.nodes[0]?.component?.refs).toBeUndefined()
|
|
1049
|
+
})
|
|
1050
|
+
|
|
1032
1051
|
it("component_loop_id の無い component ノードを拒否する", () => {
|
|
1033
1052
|
const node = { ...componentNode(), component: undefined }
|
|
1034
1053
|
expect(() =>
|
|
@@ -1068,7 +1087,7 @@ describe("saveGraphBodySchema:コンポーネントノード(component kind
|
|
|
1068
1087
|
nodes: [prog, comp],
|
|
1069
1088
|
connections: [connect(prog, comp)],
|
|
1070
1089
|
}),
|
|
1071
|
-
).toThrow(
|
|
1090
|
+
).toThrow(/適合しません/)
|
|
1072
1091
|
// 適合(string → string)なら通る
|
|
1073
1092
|
const okComp = componentNode({ type: "string" })
|
|
1074
1093
|
expect(() =>
|
package/_schemas/graph.ts
CHANGED
|
@@ -6,9 +6,7 @@ import {
|
|
|
6
6
|
portKeySchema,
|
|
7
7
|
} from "./node.js"
|
|
8
8
|
import {
|
|
9
|
-
AI_INSTRUCTION_PORT_KEY,
|
|
10
9
|
hasInboundConflict,
|
|
11
|
-
isAiEnvelopeSchema,
|
|
12
10
|
isSchemaSubset,
|
|
13
11
|
kindPortsViolation,
|
|
14
12
|
resolvePortSchemas,
|
|
@@ -148,8 +146,6 @@ export const humanConfigSchema = z.object({
|
|
|
148
146
|
// どのキーが実在するか・そのテナントが選べるかは保存時に mawaru 側が検証する
|
|
149
147
|
// (カタログは非公開。@mawaru/common の approval-view.ts)。省略時は標準ビュー
|
|
150
148
|
view: z.string().min(1).optional(),
|
|
151
|
-
// ビューに渡すパラメータ(列・ラベル・閾値など)。形はビューごとに違うので任意の JSON
|
|
152
|
-
view_config: z.unknown().nullish(),
|
|
153
149
|
assignment: z
|
|
154
150
|
.object({
|
|
155
151
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -205,6 +201,10 @@ export type HumanConfig = z.infer<typeof humanConfigSchema>
|
|
|
205
201
|
// (docs/tasks/wip/コンポーネント(ループの部品化と再利用).md)
|
|
206
202
|
export const componentConfigSchema = z.object({
|
|
207
203
|
component_loop_id: z.uuid(),
|
|
204
|
+
// 配置の設定値:子 loop の refs 定義(default/required)への上書き。
|
|
205
|
+
// 子 run 起動時の起動時指定として渡る(省略=上書きなし。検証は保存 API と
|
|
206
|
+
// 起動時の mergeRefs が行う。コンポーネントの設定値(refs)と1ノードのコンポーネント化.md)
|
|
207
|
+
refs: z.record(z.string(), z.unknown()).optional(),
|
|
208
208
|
})
|
|
209
209
|
export type ComponentConfig = z.infer<typeof componentConfigSchema>
|
|
210
210
|
|
|
@@ -318,19 +318,18 @@ export const saveGraphBodySchema = z
|
|
|
318
318
|
else outCount++
|
|
319
319
|
})
|
|
320
320
|
// end は「in は1つ以上」(複数ルートを別々の in で集約する)。
|
|
321
|
-
// ai
|
|
321
|
+
// ai も1つ以上(in は自由ポート:追加・削除・key 自由)。他 kind は1つ固定。
|
|
322
322
|
// key の妥当性は kindPortsViolation が別途見る
|
|
323
|
-
const expectIn = node.kind === "ai" ? 2 : 1
|
|
324
323
|
const inCountOk =
|
|
325
|
-
node.kind === "end" ? inCount >= 1 : inCount ===
|
|
324
|
+
node.kind === "end" || node.kind === "ai" ? inCount >= 1 : inCount === 1
|
|
326
325
|
if (!inCountOk) {
|
|
327
326
|
ctx.addIssue({
|
|
328
327
|
code: "custom",
|
|
329
328
|
path: ["nodes", i, "ports"],
|
|
330
329
|
message:
|
|
331
|
-
node.kind === "end"
|
|
330
|
+
node.kind === "end" || node.kind === "ai"
|
|
332
331
|
? "in ポートが1つ以上必要です"
|
|
333
|
-
:
|
|
332
|
+
: "in ポートはちょうど1つ必要です",
|
|
334
333
|
})
|
|
335
334
|
}
|
|
336
335
|
if (outCount < 1) {
|
|
@@ -373,7 +372,8 @@ export const saveGraphBodySchema = z
|
|
|
373
372
|
})
|
|
374
373
|
}
|
|
375
374
|
|
|
376
|
-
// kind 別のポート構成(program 以外は数・key・io 固定。ポートのkind別仕様.md
|
|
375
|
+
// kind 別のポート構成(program 以外は数・key・io 固定。ポートのkind別仕様.md)。
|
|
376
|
+
// ポートのスキーマ自体は kind によらず任意の JSON Schema(形の強制はしない)
|
|
377
377
|
const violation = kindPortsViolation(node.kind, node.ports)
|
|
378
378
|
if (violation) {
|
|
379
379
|
ctx.addIssue({
|
|
@@ -382,21 +382,6 @@ export const saveGraphBodySchema = z
|
|
|
382
382
|
message: violation,
|
|
383
383
|
})
|
|
384
384
|
}
|
|
385
|
-
|
|
386
|
-
// AI の出力宣言は封筒型 { data: T, description: string }(T は任意の JSON Schema)。
|
|
387
|
-
// 複数出口でも全出口に強制する(どの出口も human に接続できる不変条件)
|
|
388
|
-
if (node.kind === "ai") {
|
|
389
|
-
node.ports.forEach((port, j) => {
|
|
390
|
-
if (port.io === "out" && !isAiEnvelopeSchema(port.schema)) {
|
|
391
|
-
ctx.addIssue({
|
|
392
|
-
code: "custom",
|
|
393
|
-
path: ["nodes", i, "ports", j],
|
|
394
|
-
message:
|
|
395
|
-
"AI の出力は封筒型 { data, description } で宣言してください",
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
385
|
})
|
|
401
386
|
|
|
402
387
|
// 接続:両端がグラフ内の port を指し、from=out / to=in。
|
|
@@ -482,64 +467,22 @@ export const saveGraphBodySchema = z
|
|
|
482
467
|
})
|
|
483
468
|
}
|
|
484
469
|
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
if (
|
|
489
|
-
fromNode.kind === "human" &&
|
|
490
|
-
from.key === "reject" &&
|
|
491
|
-
!(toNode.kind === "ai" && to.key === AI_INSTRUCTION_PORT_KEY)
|
|
492
|
-
) {
|
|
493
|
-
ctx.addIssue({
|
|
494
|
-
code: "custom",
|
|
495
|
-
path: ["connections", i, "to_port_id"],
|
|
496
|
-
message:
|
|
497
|
-
"差し戻し(reject)の接続先は AI ノードの instruction ポートのみです",
|
|
498
|
-
})
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// instruction ポートに繋げるのは human の reject だけ(データを流し込ませない)
|
|
502
|
-
if (
|
|
503
|
-
toNode.kind === "ai" &&
|
|
504
|
-
to.key === AI_INSTRUCTION_PORT_KEY &&
|
|
505
|
-
!(fromNode.kind === "human" && from.key === "reject")
|
|
506
|
-
) {
|
|
507
|
-
ctx.addIssue({
|
|
508
|
-
code: "custom",
|
|
509
|
-
path: ["connections", i, "to_port_id"],
|
|
510
|
-
message:
|
|
511
|
-
"instruction ポートに接続できるのは human の差し戻し(reject)だけです",
|
|
512
|
-
})
|
|
513
|
-
}
|
|
470
|
+
// 差し戻し(reject)と instruction の組み合わせに kind の縛りも型固定も無い:
|
|
471
|
+
// reject の型はビューの宣言、instruction は ai の in の1つ(接続鏡映)でしかない
|
|
472
|
+
// → docs/tasks/wip/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
514
473
|
|
|
474
|
+
// 型検証は kind によらず常に 伝播型(out) ⊆ 伝播型(in)。
|
|
475
|
+
// 未宣言は unknown=判断できないので通す(作りかけのグラフを保存できなくしない)。
|
|
476
|
+
// 接続鏡映する口(program / ai の in)とコピー宣言のビューは接続元の型を採用する
|
|
477
|
+
// ので自明に通り、実質の検証対象は宣言で固定された口
|
|
478
|
+
// (component の契約・宣言のあるビューの human.in)になる
|
|
515
479
|
const fromSchema = resolved.get(conn.from_port_id) ?? from.schema
|
|
516
|
-
|
|
517
|
-
// human の in はどの型でも接続できる(上流が AI なら封筒 { data, description }、
|
|
518
|
-
// それ以外は input 全体を data として表示する。型制約は課さない)
|
|
519
|
-
|
|
520
|
-
// program の in への接続は 伝播型 ⊆ 宣言スキーマ。in は接続鏡映(sticky)なので
|
|
521
|
-
// 具体型の接続では自明に通り、接続元が未宣言 × 宣言維持のときだけ従来の意味を持つ
|
|
522
|
-
// (未宣言は unknown=判断できないので通す。作りかけのグラフを保存できなくしない)
|
|
523
480
|
const toSchema = resolved.get(conn.to_port_id) ?? to.schema
|
|
524
|
-
if (
|
|
525
|
-
ctx.addIssue({
|
|
526
|
-
code: "custom",
|
|
527
|
-
path: ["connections", i, "to_port_id"],
|
|
528
|
-
message:
|
|
529
|
-
"接続元の型が program の入力スキーマに適合しません(伝播型 ⊆ 宣言スキーマ)",
|
|
530
|
-
})
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// component の in は子 loop の契約(焼き込み済み宣言)。program と同じ包含チェック
|
|
534
|
-
if (
|
|
535
|
-
toNode.kind === "component" &&
|
|
536
|
-
!isSchemaSubset(fromSchema, toSchema)
|
|
537
|
-
) {
|
|
481
|
+
if (!isSchemaSubset(fromSchema, toSchema)) {
|
|
538
482
|
ctx.addIssue({
|
|
539
483
|
code: "custom",
|
|
540
484
|
path: ["connections", i, "to_port_id"],
|
|
541
|
-
message:
|
|
542
|
-
"接続元の型がコンポーネントの入力契約に適合しません(伝播型 ⊆ 契約)",
|
|
485
|
+
message: `「${fromNode.name}」の出力が「${toNode.name}」の入力スキーマに適合しません`,
|
|
543
486
|
})
|
|
544
487
|
}
|
|
545
488
|
})
|
package/_schemas/node.ts
CHANGED
|
@@ -47,6 +47,10 @@ export const graphPortSchema = z
|
|
|
47
47
|
key: portKeySchema,
|
|
48
48
|
name: z.string().min(1),
|
|
49
49
|
schema: jsonSchemaSchema,
|
|
50
|
+
// in ポートの実行属性:true ならこの口から届いた実行は前回セッションを復元して
|
|
51
|
+
// 再開する(AI ノードで意味を持つ。key ではなくこの属性が再開を決める。
|
|
52
|
+
// ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
53
|
+
resume: z.boolean().optional(),
|
|
50
54
|
side: portSideSchema.optional(),
|
|
51
55
|
offset: z.number().min(0).max(1).optional(),
|
|
52
56
|
})
|