@mawaru/sdk 0.33.0 → 0.35.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 +29 -10
- package/_schemas/ai-manifest.ts +22 -6
- package/_schemas/graph.test.ts +48 -0
- package/_schemas/graph.ts +10 -7
- package/_schemas/node.ts +3 -0
- package/_schemas/port-spec.test.ts +105 -0
- package/_schemas/port-spec.ts +45 -1
- package/dist/_schemas/ai-manifest.d.ts +28 -7
- package/dist/_schemas/ai-manifest.js +22 -6
- package/dist/_schemas/graph.d.ts +4 -0
- package/dist/_schemas/graph.js +7 -3
- package/dist/_schemas/node.d.ts +1 -0
- package/dist/_schemas/node.js +3 -0
- package/dist/_schemas/port-spec.js +37 -1
- package/docs/development.md +4 -2
- package/package.json +1 -1
- package/skills/create-loop/SKILL.md +6 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { MODEL_SERIES } from "../../agent-runner/_agents/models.js"
|
|
2
3
|
import {
|
|
3
4
|
AI_MODEL_GROUPS,
|
|
5
|
+
AI_MODEL_SERIES,
|
|
4
6
|
AI_MODELS,
|
|
5
7
|
agentOfModel,
|
|
6
8
|
aiManifestSchema,
|
|
@@ -52,12 +54,12 @@ describe("aiManifestSchema", () => {
|
|
|
52
54
|
outputs: { main: textOutput },
|
|
53
55
|
})
|
|
54
56
|
expect(manifest.name).toBeUndefined()
|
|
55
|
-
expect(manifest.model).toBe("claude-opus
|
|
57
|
+
expect(manifest.model).toBe("claude-opus")
|
|
56
58
|
expect(manifest.skills).toEqual([])
|
|
57
59
|
expect(manifest.env).toEqual([])
|
|
58
60
|
})
|
|
59
61
|
|
|
60
|
-
it("model は AI_MODELS の enum
|
|
62
|
+
it("model は AI_MODELS の enum(系列か固定 ID。外の値は拒否)", () => {
|
|
61
63
|
for (const model of AI_MODELS) {
|
|
62
64
|
expect(aiManifestSchema.parse({ ...valid, model }).model).toBe(model)
|
|
63
65
|
}
|
|
@@ -80,19 +82,36 @@ describe("aiManifestSchema", () => {
|
|
|
80
82
|
expect(
|
|
81
83
|
aiManifestSchema.parse({ ...valid, model: "gpt-6-astra" }).model,
|
|
82
84
|
).toBe("gpt-6-astra")
|
|
83
|
-
|
|
85
|
+
const { claude, codex } = AI_MODEL_GROUPS
|
|
86
|
+
for (const model of [...claude.series, ...claude.models]) {
|
|
84
87
|
expect(agentOfModel(model)).toBe("claude")
|
|
85
88
|
}
|
|
86
|
-
for (const model of
|
|
89
|
+
for (const model of [...codex.series, ...codex.models]) {
|
|
87
90
|
expect(agentOfModel(model)).toBe("codex")
|
|
88
91
|
}
|
|
89
|
-
//
|
|
90
|
-
expect(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// 各グループの既定はそのグループの系列(最新追従)
|
|
93
|
+
expect(claude.series).toContain(claude.default)
|
|
94
|
+
expect(codex.series).toContain(codex.default)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("系列(最新追従)を選べる。既定は claude-opus", () => {
|
|
98
|
+
for (const model of ["claude-opus", "gpt-sol"]) {
|
|
99
|
+
expect(aiManifestSchema.parse({ ...valid, model }).model).toBe(model)
|
|
100
|
+
}
|
|
101
|
+
expect(AI_MODEL_GROUPS.claude.default).toBe("claude-opus")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("系列の一覧は runner の対応表と一致し、読み替え先は同じグループの固定 ID", () => {
|
|
105
|
+
// runner は npm 公開パッケージで sdk に依存できず、系列名は二重管理になる。
|
|
106
|
+
// どちらかに足し忘れたらここで落ちる
|
|
107
|
+
expect(Object.keys(MODEL_SERIES).sort()).toEqual(
|
|
108
|
+
[...AI_MODEL_SERIES].sort(),
|
|
95
109
|
)
|
|
110
|
+
for (const group of Object.values(AI_MODEL_GROUPS)) {
|
|
111
|
+
for (const series of group.series) {
|
|
112
|
+
expect(group.models).toContain(MODEL_SERIES[series])
|
|
113
|
+
}
|
|
114
|
+
}
|
|
96
115
|
})
|
|
97
116
|
|
|
98
117
|
it("inputs は key 自由・1件以上(in / instruction の固定構成は廃止)", () => {
|
package/_schemas/ai-manifest.ts
CHANGED
|
@@ -6,12 +6,19 @@ import { jsonSchemaSchema, PORT_KEY_PATTERN } from "./node.js"
|
|
|
6
6
|
// config.json に agent のようなフィールドは持たず、model の接頭辞(claude-* / gpt-*)で決まる
|
|
7
7
|
// (runner の _agents/index.ts と同じ規則。AI実行エージェントの抽象化(Claude CodeとCodex).md)。
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// model に書けるのは「系列」か「固定 ID」のどちらか
|
|
10
|
+
// (docs/tasks: AIノードのモデルを常に最新にする(系列指定).md)。
|
|
11
|
+
// - series=系列(claude-opus 等)。その系列の最新モデルで走る。どの実 ID に当たるかは
|
|
12
|
+
// runner の対応表(agent-runner の _agents/models.ts)が実行時に決めるので、新モデルが
|
|
13
|
+
// 出たら対応表を直すだけで全ノードが追従する(各 repo の config.json は触らない)。
|
|
14
|
+
// 系列名の一覧はここと runner の二重管理(ai-manifest.test.ts が一致を検証する)
|
|
15
|
+
// - models=固定 ID。そのバージョンで走り続ける(新モデルで崩れたノードを戻す手段)
|
|
16
|
+
// 系列をまたいでは上がらない(費用はユーザー持ち。Opus 指定が Fable になることは無い)。
|
|
17
|
+
// 既定は Opus の系列:下書き品質が価値の中心
|
|
12
18
|
export const AI_MODEL_GROUPS = {
|
|
13
19
|
claude: {
|
|
14
20
|
name: "Claude Code",
|
|
21
|
+
series: ["claude-fable", "claude-opus", "claude-sonnet", "claude-haiku"],
|
|
15
22
|
models: [
|
|
16
23
|
"claude-fable-5-1",
|
|
17
24
|
"claude-fable-5",
|
|
@@ -20,25 +27,34 @@ export const AI_MODEL_GROUPS = {
|
|
|
20
27
|
"claude-sonnet-5",
|
|
21
28
|
"claude-haiku-4-5",
|
|
22
29
|
],
|
|
23
|
-
default: "claude-opus
|
|
30
|
+
default: "claude-opus",
|
|
24
31
|
},
|
|
32
|
+
// astra=最上位 / sol=主力 / luna=軽量。terra は GPT-5.6 世代にしか無いので系列にしない
|
|
25
33
|
codex: {
|
|
26
34
|
name: "Codex",
|
|
35
|
+
series: ["gpt-astra", "gpt-sol", "gpt-luna"],
|
|
27
36
|
models: [
|
|
37
|
+
"gpt-6.1-sol",
|
|
28
38
|
"gpt-6-astra",
|
|
39
|
+
"gpt-6-sol",
|
|
40
|
+
"gpt-6-luna",
|
|
29
41
|
"gpt-5.6-sol",
|
|
30
42
|
"gpt-5.6-terra",
|
|
31
43
|
"gpt-5.6-luna",
|
|
32
|
-
"gpt-5.5",
|
|
33
44
|
],
|
|
34
|
-
default: "gpt-
|
|
45
|
+
default: "gpt-astra",
|
|
35
46
|
},
|
|
36
47
|
} as const
|
|
37
48
|
export const AI_AGENT_IDS = ["claude", "codex"] as const
|
|
38
49
|
export const aiAgentIdSchema = z.enum(AI_AGENT_IDS)
|
|
39
50
|
export type AiAgentId = z.infer<typeof aiAgentIdSchema>
|
|
40
51
|
|
|
52
|
+
export const AI_MODEL_SERIES = [
|
|
53
|
+
...AI_MODEL_GROUPS.claude.series,
|
|
54
|
+
...AI_MODEL_GROUPS.codex.series,
|
|
55
|
+
] as const
|
|
41
56
|
export const AI_MODELS = [
|
|
57
|
+
...AI_MODEL_SERIES,
|
|
42
58
|
...AI_MODEL_GROUPS.claude.models,
|
|
43
59
|
...AI_MODEL_GROUPS.codex.models,
|
|
44
60
|
] as const
|
package/_schemas/graph.test.ts
CHANGED
|
@@ -1016,6 +1016,54 @@ describe("saveGraphBodySchema:開始/終了ノード(start/end kind)", ()
|
|
|
1016
1016
|
})
|
|
1017
1017
|
})
|
|
1018
1018
|
|
|
1019
|
+
// 合流は in を複数持てる(key 自由)+ out(main)
|
|
1020
|
+
const joinNode = (inKeys: string[] = ["in_1", "in_2"]): TestNode => ({
|
|
1021
|
+
...makeNode([...inKeys.map((k) => port("in", k)), port("out", "main")]),
|
|
1022
|
+
kind: "join",
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
describe("saveGraphBodySchema:合流ノード(join kind)", () => {
|
|
1026
|
+
it("2本の枝を合流させるグラフを受け付ける", () => {
|
|
1027
|
+
const a = makeNode(simplePorts())
|
|
1028
|
+
const b = makeNode(simplePorts())
|
|
1029
|
+
const j = joinNode()
|
|
1030
|
+
const [in1, in2] = j.ports.filter((p) => p.io === "in")
|
|
1031
|
+
const parsed = saveGraphBodySchema.parse({
|
|
1032
|
+
nodes: [a, b, j],
|
|
1033
|
+
connections: [
|
|
1034
|
+
{ from_port_id: outOf(a).id, to_port_id: in1?.id },
|
|
1035
|
+
{ from_port_id: outOf(b).id, to_port_id: in2?.id },
|
|
1036
|
+
],
|
|
1037
|
+
})
|
|
1038
|
+
expect(parsed.nodes.map((n) => n.kind)).toContain("join")
|
|
1039
|
+
})
|
|
1040
|
+
|
|
1041
|
+
it("合流は複数置ける", () => {
|
|
1042
|
+
expect(() =>
|
|
1043
|
+
saveGraphBodySchema.parse({
|
|
1044
|
+
nodes: [joinNode(), joinNode()],
|
|
1045
|
+
connections: [],
|
|
1046
|
+
}),
|
|
1047
|
+
).not.toThrow()
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
it("in が無い合流を拒否する", () => {
|
|
1051
|
+
expect(() =>
|
|
1052
|
+
saveGraphBodySchema.parse({ nodes: [joinNode([])], connections: [] }),
|
|
1053
|
+
).toThrow(/in ポートが1つ以上/)
|
|
1054
|
+
})
|
|
1055
|
+
|
|
1056
|
+
it("out は main ちょうど1つ", () => {
|
|
1057
|
+
const bad: TestNode = {
|
|
1058
|
+
...joinNode(),
|
|
1059
|
+
ports: [port("in", "in_1"), port("out", "main"), port("out", "extra")],
|
|
1060
|
+
}
|
|
1061
|
+
expect(() =>
|
|
1062
|
+
saveGraphBodySchema.parse({ nodes: [bad], connections: [] }),
|
|
1063
|
+
).toThrow(/join/)
|
|
1064
|
+
})
|
|
1065
|
+
})
|
|
1066
|
+
|
|
1019
1067
|
describe("refsDefaultsOf(定義スキーマ→デフォルト値の袋)", () => {
|
|
1020
1068
|
it("default を持つプロパティだけを値の袋に集める", () => {
|
|
1021
1069
|
const defaults = refsDefaultsOf({
|
package/_schemas/graph.ts
CHANGED
|
@@ -74,6 +74,7 @@ export const editableNodeKindSchema = nodeKindSchema.extract([
|
|
|
74
74
|
"human",
|
|
75
75
|
"program",
|
|
76
76
|
"wait",
|
|
77
|
+
"join",
|
|
77
78
|
])
|
|
78
79
|
export type EditableNodeKind = z.infer<typeof editableNodeKindSchema>
|
|
79
80
|
|
|
@@ -87,6 +88,7 @@ export const graphNodeKindSchema = nodeKindSchema.extract([
|
|
|
87
88
|
"start",
|
|
88
89
|
"end",
|
|
89
90
|
"component",
|
|
91
|
+
"join",
|
|
90
92
|
])
|
|
91
93
|
export type GraphNodeKind = z.infer<typeof graphNodeKindSchema>
|
|
92
94
|
|
|
@@ -384,18 +386,19 @@ export const saveGraphBodySchema = z
|
|
|
384
386
|
else outCount++
|
|
385
387
|
})
|
|
386
388
|
// end は「in は1つ以上」(複数ルートを別々の in で集約する)。
|
|
387
|
-
// ai も1つ以上(in は自由ポート:追加・削除・key
|
|
389
|
+
// ai も1つ以上(in は自由ポート:追加・削除・key 自由)。join も1つ以上
|
|
390
|
+
// (待ち合わせる枝ごとに in)。他 kind は1つ固定。
|
|
388
391
|
// key の妥当性は kindPortsViolation が別途見る
|
|
389
|
-
const
|
|
390
|
-
node.kind === "end" || node.kind === "ai"
|
|
392
|
+
const multiIn =
|
|
393
|
+
node.kind === "end" || node.kind === "ai" || node.kind === "join"
|
|
394
|
+
const inCountOk = multiIn ? inCount >= 1 : inCount === 1
|
|
391
395
|
if (!inCountOk) {
|
|
392
396
|
ctx.addIssue({
|
|
393
397
|
code: "custom",
|
|
394
398
|
path: ["nodes", i, "ports"],
|
|
395
|
-
message:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
: "in ポートはちょうど1つ必要です",
|
|
399
|
+
message: multiIn
|
|
400
|
+
? "in ポートが1つ以上必要です"
|
|
401
|
+
: "in ポートはちょうど1つ必要です",
|
|
399
402
|
})
|
|
400
403
|
}
|
|
401
404
|
if (outCount < 1) {
|
package/_schemas/node.ts
CHANGED
|
@@ -13,6 +13,9 @@ export const nodeKindSchema = z.enum([
|
|
|
13
13
|
// コンポーネント(kind='component' の loop を配置して呼ぶ。子 run・戻り値あり。
|
|
14
14
|
// docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
15
15
|
"component",
|
|
16
|
+
// 合流(線のつながった入口が全部揃うまで待ち、key ごとにまとめて流す。
|
|
17
|
+
// docs/tasks/wip/合流ノード(並行分岐の待ち合わせ).md)
|
|
18
|
+
"join",
|
|
16
19
|
])
|
|
17
20
|
export type NodeKind = z.infer<typeof nodeKindSchema>
|
|
18
21
|
|
|
@@ -782,6 +782,111 @@ describe("型伝播:fan-in(同じ in ポートへの流入2本以上)", ()
|
|
|
782
782
|
})
|
|
783
783
|
})
|
|
784
784
|
|
|
785
|
+
describe("合流(join)", () => {
|
|
786
|
+
const findings = {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: { findings: { type: "array" } },
|
|
789
|
+
required: ["findings"],
|
|
790
|
+
}
|
|
791
|
+
const source = (schema: JsonSchema) => {
|
|
792
|
+
const out = port("out", "main", schema)
|
|
793
|
+
return {
|
|
794
|
+
node: { id: uuid(), kind: "program", ports: [port("in", "in"), out] },
|
|
795
|
+
out,
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const named = (key: string, name: string): GraphPort => ({
|
|
799
|
+
...port("in", key),
|
|
800
|
+
name,
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
it("join は in の key 自由・1つ以上、out は main ちょうど1つ", () => {
|
|
804
|
+
expect(
|
|
805
|
+
kindPortsViolation("join", [
|
|
806
|
+
port("in", "in_1"),
|
|
807
|
+
port("in", "in_2"),
|
|
808
|
+
port("out", "main"),
|
|
809
|
+
]),
|
|
810
|
+
).toBeNull()
|
|
811
|
+
expect(
|
|
812
|
+
kindPortsViolation("join", [port("in", "in_1"), port("out", "done")]),
|
|
813
|
+
).toMatch(/join/)
|
|
814
|
+
expect(
|
|
815
|
+
kindPortsViolation("join", [
|
|
816
|
+
port("in", "in_1"),
|
|
817
|
+
port("out", "main"),
|
|
818
|
+
port("out", "extra"),
|
|
819
|
+
]),
|
|
820
|
+
).toMatch(/join/)
|
|
821
|
+
expect(kindPortsViolation("join", [port("out", "main")])).toMatch(/join/)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
it("各 in は自分の上流から、out main は線のつながった in を key ごとに持つ object", () => {
|
|
825
|
+
const a = source(findings)
|
|
826
|
+
const b = source({ type: "string" })
|
|
827
|
+
const in1 = named("in_1", "スキャン")
|
|
828
|
+
const in2 = named("in_2", "依存チェック")
|
|
829
|
+
const loose = named("in_3", "未接続")
|
|
830
|
+
const out = port("out", "main")
|
|
831
|
+
const join = { id: uuid(), kind: "join", ports: [in1, in2, loose, out] }
|
|
832
|
+
const resolved = resolvePortSchemas(
|
|
833
|
+
[a.node, b.node, join],
|
|
834
|
+
[
|
|
835
|
+
{ from_port_id: a.out.id, to_port_id: in1.id },
|
|
836
|
+
{ from_port_id: b.out.id, to_port_id: in2.id },
|
|
837
|
+
],
|
|
838
|
+
)
|
|
839
|
+
expect(resolved.get(in1.id)).toEqual(findings)
|
|
840
|
+
expect(resolved.get(in2.id)).toEqual({ type: "string" })
|
|
841
|
+
// 未接続の in は待たない=出口にも出ない
|
|
842
|
+
expect(resolved.get(out.id)).toEqual({
|
|
843
|
+
type: "object",
|
|
844
|
+
properties: {
|
|
845
|
+
in_1: { ...findings, title: "スキャン" },
|
|
846
|
+
in_2: { type: "string", title: "依存チェック" },
|
|
847
|
+
},
|
|
848
|
+
required: ["in_1", "in_2"],
|
|
849
|
+
})
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
it("out main の型は下流の program の in へ鏡映される", () => {
|
|
853
|
+
const a = source(findings)
|
|
854
|
+
const in1 = port("in", "in_1")
|
|
855
|
+
const out = port("out", "main")
|
|
856
|
+
const join = { id: uuid(), kind: "join", ports: [in1, out] }
|
|
857
|
+
const nextIn = port("in", "in")
|
|
858
|
+
const next = {
|
|
859
|
+
id: uuid(),
|
|
860
|
+
kind: "program",
|
|
861
|
+
ports: [nextIn, port("out", "main")],
|
|
862
|
+
}
|
|
863
|
+
const resolved = resolvePortSchemas(
|
|
864
|
+
[a.node, join, next],
|
|
865
|
+
[
|
|
866
|
+
{ from_port_id: a.out.id, to_port_id: in1.id },
|
|
867
|
+
{ from_port_id: out.id, to_port_id: nextIn.id },
|
|
868
|
+
],
|
|
869
|
+
)
|
|
870
|
+
expect(resolved.get(nextIn.id)).toEqual({
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: { in_1: { ...findings, title: "in_1" } },
|
|
873
|
+
required: ["in_1"],
|
|
874
|
+
})
|
|
875
|
+
})
|
|
876
|
+
|
|
877
|
+
it("線が1本も無ければ out main は未宣言", () => {
|
|
878
|
+
const out = port("out", "main")
|
|
879
|
+
const join = {
|
|
880
|
+
id: uuid(),
|
|
881
|
+
kind: "join",
|
|
882
|
+
ports: [port("in", "in_1"), out],
|
|
883
|
+
}
|
|
884
|
+
expect(resolvePortSchemas([join], []).get(out.id)).toEqual(
|
|
885
|
+
UNDECLARED_SCHEMA,
|
|
886
|
+
)
|
|
887
|
+
})
|
|
888
|
+
})
|
|
889
|
+
|
|
785
890
|
describe("file 型(nominal)", () => {
|
|
786
891
|
it("fileSchema はマーカー付き object、isFileSchema で識別できる", () => {
|
|
787
892
|
const schema = fileSchema()
|
package/_schemas/port-spec.ts
CHANGED
|
@@ -198,6 +198,19 @@ export const kindPortsViolation = (
|
|
|
198
198
|
}
|
|
199
199
|
return null
|
|
200
200
|
}
|
|
201
|
+
// join は out が main 1つ固定・in は1つ以上(待ち合わせる枝ごとに in。key 自由。
|
|
202
|
+
// 合流ノード(並行分岐の待ち合わせ).md)
|
|
203
|
+
if (kind === "join") {
|
|
204
|
+
const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key)
|
|
205
|
+
if (
|
|
206
|
+
outKeys.length !== 1 ||
|
|
207
|
+
outKeys[0] !== "main" ||
|
|
208
|
+
!ports.some((p) => p.io === "in")
|
|
209
|
+
) {
|
|
210
|
+
return `join のポート構成は in 1つ以上と out "main" ちょうど1つです`
|
|
211
|
+
}
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
201
214
|
if (kind === "human") {
|
|
202
215
|
const inKeys = ports.filter((p) => p.io === "in").map((p) => p.key)
|
|
203
216
|
const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key)
|
|
@@ -338,7 +351,8 @@ export type PropagationOptions<N> = {
|
|
|
338
351
|
// resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
|
|
339
352
|
// start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
|
|
340
353
|
// (run 入力契約=接続先の入力宣言。開始ノードの入力契約を接続先から導出.md)
|
|
341
|
-
|
|
354
|
+
// join も導出ノード(各 in は上流から、out main は線のつながった in を key ごとに持つ object)
|
|
355
|
+
const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"])
|
|
342
356
|
|
|
343
357
|
// in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
|
|
344
358
|
const MIRROR_KINDS = new Set(["program", "ai"])
|
|
@@ -428,6 +442,36 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
428
442
|
}
|
|
429
443
|
continue
|
|
430
444
|
}
|
|
445
|
+
// join:各 in を自分の上流から導出(end と同じ)。out main は線のつながった in を
|
|
446
|
+
// key ごとに持つ object(「両方」を1段入れて持つ。中身を平らに混ぜると key が
|
|
447
|
+
// ぶつかる)。待つのは線のつながった in だけなので、未接続の in は出口に出さない
|
|
448
|
+
if (node.kind === "join") {
|
|
449
|
+
const properties: Record<string, JsonSchema> = {}
|
|
450
|
+
for (const port of node.ports) {
|
|
451
|
+
if (port.io !== "in") continue
|
|
452
|
+
const srcs = inbound.get(port.id) ?? []
|
|
453
|
+
const schema = mergeInboundSchemas(
|
|
454
|
+
srcs.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA),
|
|
455
|
+
)
|
|
456
|
+
assign(port.id, schema)
|
|
457
|
+
if (srcs.length > 0) {
|
|
458
|
+
properties[port.key] = { ...schema, title: port.name }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const outMain = node.ports.find(
|
|
462
|
+
(p) => p.io === "out" && p.key === "main",
|
|
463
|
+
)
|
|
464
|
+
const keys = Object.keys(properties)
|
|
465
|
+
if (outMain) {
|
|
466
|
+
assign(
|
|
467
|
+
outMain.id,
|
|
468
|
+
keys.length > 0
|
|
469
|
+
? { type: "object", properties, required: keys }
|
|
470
|
+
: UNDECLARED_SCHEMA,
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
continue
|
|
474
|
+
}
|
|
431
475
|
const isMirror = MIRROR_KINDS.has(node.kind)
|
|
432
476
|
if (!DERIVED_KINDS.has(node.kind) && !isMirror) continue
|
|
433
477
|
// 鏡映(program / ai):全ての in が対象。具体型が流れてきたときだけ上書きする
|
|
@@ -2,13 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const AI_MODEL_GROUPS: {
|
|
3
3
|
readonly claude: {
|
|
4
4
|
readonly name: "Claude Code";
|
|
5
|
+
readonly series: readonly ["claude-fable", "claude-opus", "claude-sonnet", "claude-haiku"];
|
|
5
6
|
readonly models: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
|
|
6
|
-
readonly default: "claude-opus
|
|
7
|
+
readonly default: "claude-opus";
|
|
7
8
|
};
|
|
8
9
|
readonly codex: {
|
|
9
10
|
readonly name: "Codex";
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
11
|
+
readonly series: readonly ["gpt-astra", "gpt-sol", "gpt-luna"];
|
|
12
|
+
readonly models: readonly ["gpt-6.1-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
13
|
+
readonly default: "gpt-astra";
|
|
12
14
|
};
|
|
13
15
|
};
|
|
14
16
|
export declare const AI_AGENT_IDS: readonly ["claude", "codex"];
|
|
@@ -17,22 +19,32 @@ export declare const aiAgentIdSchema: z.ZodEnum<{
|
|
|
17
19
|
codex: "codex";
|
|
18
20
|
}>;
|
|
19
21
|
export type AiAgentId = z.infer<typeof aiAgentIdSchema>;
|
|
20
|
-
export declare const
|
|
22
|
+
export declare const AI_MODEL_SERIES: readonly ["claude-fable", "claude-opus", "claude-sonnet", "claude-haiku", "gpt-astra", "gpt-sol", "gpt-luna"];
|
|
23
|
+
export declare const AI_MODELS: readonly ["claude-fable", "claude-opus", "claude-sonnet", "claude-haiku", "gpt-astra", "gpt-sol", "gpt-luna", "claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5", "gpt-6.1-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
21
24
|
export declare const aiModelSchema: z.ZodEnum<{
|
|
25
|
+
"claude-fable": "claude-fable";
|
|
26
|
+
"claude-opus": "claude-opus";
|
|
27
|
+
"claude-sonnet": "claude-sonnet";
|
|
28
|
+
"claude-haiku": "claude-haiku";
|
|
22
29
|
"claude-fable-5-1": "claude-fable-5-1";
|
|
23
30
|
"claude-fable-5": "claude-fable-5";
|
|
24
31
|
"claude-opus-5": "claude-opus-5";
|
|
25
32
|
"claude-opus-4-8": "claude-opus-4-8";
|
|
26
33
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
27
34
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
35
|
+
"gpt-astra": "gpt-astra";
|
|
36
|
+
"gpt-sol": "gpt-sol";
|
|
37
|
+
"gpt-luna": "gpt-luna";
|
|
38
|
+
"gpt-6.1-sol": "gpt-6.1-sol";
|
|
28
39
|
"gpt-6-astra": "gpt-6-astra";
|
|
40
|
+
"gpt-6-sol": "gpt-6-sol";
|
|
41
|
+
"gpt-6-luna": "gpt-6-luna";
|
|
29
42
|
"gpt-5.6-sol": "gpt-5.6-sol";
|
|
30
43
|
"gpt-5.6-terra": "gpt-5.6-terra";
|
|
31
44
|
"gpt-5.6-luna": "gpt-5.6-luna";
|
|
32
|
-
"gpt-5.5": "gpt-5.5";
|
|
33
45
|
}>;
|
|
34
46
|
export type AiModel = z.infer<typeof aiModelSchema>;
|
|
35
|
-
export declare const DEFAULT_AI_MODEL: "claude-opus
|
|
47
|
+
export declare const DEFAULT_AI_MODEL: "claude-opus";
|
|
36
48
|
export declare const agentOfModel: (model: AiModel) => AiAgentId;
|
|
37
49
|
export declare const aiInputDeclSchema: z.ZodObject<{
|
|
38
50
|
schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
@@ -44,17 +56,26 @@ export declare const aiManifestSchema: z.ZodObject<{
|
|
|
44
56
|
description: z.ZodOptional<z.ZodString>;
|
|
45
57
|
prompt: z.ZodString;
|
|
46
58
|
model: z.ZodDefault<z.ZodEnum<{
|
|
59
|
+
"claude-fable": "claude-fable";
|
|
60
|
+
"claude-opus": "claude-opus";
|
|
61
|
+
"claude-sonnet": "claude-sonnet";
|
|
62
|
+
"claude-haiku": "claude-haiku";
|
|
47
63
|
"claude-fable-5-1": "claude-fable-5-1";
|
|
48
64
|
"claude-fable-5": "claude-fable-5";
|
|
49
65
|
"claude-opus-5": "claude-opus-5";
|
|
50
66
|
"claude-opus-4-8": "claude-opus-4-8";
|
|
51
67
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
52
68
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
69
|
+
"gpt-astra": "gpt-astra";
|
|
70
|
+
"gpt-sol": "gpt-sol";
|
|
71
|
+
"gpt-luna": "gpt-luna";
|
|
72
|
+
"gpt-6.1-sol": "gpt-6.1-sol";
|
|
53
73
|
"gpt-6-astra": "gpt-6-astra";
|
|
74
|
+
"gpt-6-sol": "gpt-6-sol";
|
|
75
|
+
"gpt-6-luna": "gpt-6-luna";
|
|
54
76
|
"gpt-5.6-sol": "gpt-5.6-sol";
|
|
55
77
|
"gpt-5.6-terra": "gpt-5.6-terra";
|
|
56
78
|
"gpt-5.6-luna": "gpt-5.6-luna";
|
|
57
|
-
"gpt-5.5": "gpt-5.5";
|
|
58
79
|
}>>;
|
|
59
80
|
auth_mode: z.ZodOptional<z.ZodEnum<{
|
|
60
81
|
subscription: "subscription";
|
|
@@ -5,12 +5,19 @@ import { jsonSchemaSchema, PORT_KEY_PATTERN } from "./node.js";
|
|
|
5
5
|
// config.json に agent のようなフィールドは持たず、model の接頭辞(claude-* / gpt-*)で決まる
|
|
6
6
|
// (runner の _agents/index.ts と同じ規則。AI実行エージェントの抽象化(Claude CodeとCodex).md)。
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// model に書けるのは「系列」か「固定 ID」のどちらか
|
|
9
|
+
// (docs/tasks: AIノードのモデルを常に最新にする(系列指定).md)。
|
|
10
|
+
// - series=系列(claude-opus 等)。その系列の最新モデルで走る。どの実 ID に当たるかは
|
|
11
|
+
// runner の対応表(agent-runner の _agents/models.ts)が実行時に決めるので、新モデルが
|
|
12
|
+
// 出たら対応表を直すだけで全ノードが追従する(各 repo の config.json は触らない)。
|
|
13
|
+
// 系列名の一覧はここと runner の二重管理(ai-manifest.test.ts が一致を検証する)
|
|
14
|
+
// - models=固定 ID。そのバージョンで走り続ける(新モデルで崩れたノードを戻す手段)
|
|
15
|
+
// 系列をまたいでは上がらない(費用はユーザー持ち。Opus 指定が Fable になることは無い)。
|
|
16
|
+
// 既定は Opus の系列:下書き品質が価値の中心
|
|
11
17
|
export const AI_MODEL_GROUPS = {
|
|
12
18
|
claude: {
|
|
13
19
|
name: "Claude Code",
|
|
20
|
+
series: ["claude-fable", "claude-opus", "claude-sonnet", "claude-haiku"],
|
|
14
21
|
models: [
|
|
15
22
|
"claude-fable-5-1",
|
|
16
23
|
"claude-fable-5",
|
|
@@ -19,23 +26,32 @@ export const AI_MODEL_GROUPS = {
|
|
|
19
26
|
"claude-sonnet-5",
|
|
20
27
|
"claude-haiku-4-5",
|
|
21
28
|
],
|
|
22
|
-
default: "claude-opus
|
|
29
|
+
default: "claude-opus",
|
|
23
30
|
},
|
|
31
|
+
// astra=最上位 / sol=主力 / luna=軽量。terra は GPT-5.6 世代にしか無いので系列にしない
|
|
24
32
|
codex: {
|
|
25
33
|
name: "Codex",
|
|
34
|
+
series: ["gpt-astra", "gpt-sol", "gpt-luna"],
|
|
26
35
|
models: [
|
|
36
|
+
"gpt-6.1-sol",
|
|
27
37
|
"gpt-6-astra",
|
|
38
|
+
"gpt-6-sol",
|
|
39
|
+
"gpt-6-luna",
|
|
28
40
|
"gpt-5.6-sol",
|
|
29
41
|
"gpt-5.6-terra",
|
|
30
42
|
"gpt-5.6-luna",
|
|
31
|
-
"gpt-5.5",
|
|
32
43
|
],
|
|
33
|
-
default: "gpt-
|
|
44
|
+
default: "gpt-astra",
|
|
34
45
|
},
|
|
35
46
|
};
|
|
36
47
|
export const AI_AGENT_IDS = ["claude", "codex"];
|
|
37
48
|
export const aiAgentIdSchema = z.enum(AI_AGENT_IDS);
|
|
49
|
+
export const AI_MODEL_SERIES = [
|
|
50
|
+
...AI_MODEL_GROUPS.claude.series,
|
|
51
|
+
...AI_MODEL_GROUPS.codex.series,
|
|
52
|
+
];
|
|
38
53
|
export const AI_MODELS = [
|
|
54
|
+
...AI_MODEL_SERIES,
|
|
39
55
|
...AI_MODEL_GROUPS.claude.models,
|
|
40
56
|
...AI_MODEL_GROUPS.codex.models,
|
|
41
57
|
];
|
package/dist/_schemas/graph.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type LoopRefs = z.infer<typeof loopRefsSchema>;
|
|
|
11
11
|
export declare const EMPTY_LOOP_REFS: LoopRefs;
|
|
12
12
|
export declare const refsDefaultsOf: (refs: LoopRefs) => RefsBag;
|
|
13
13
|
export declare const editableNodeKindSchema: z.ZodEnum<{
|
|
14
|
+
join: "join";
|
|
14
15
|
ai: "ai";
|
|
15
16
|
human: "human";
|
|
16
17
|
program: "program";
|
|
@@ -19,6 +20,7 @@ export declare const editableNodeKindSchema: z.ZodEnum<{
|
|
|
19
20
|
export type EditableNodeKind = z.infer<typeof editableNodeKindSchema>;
|
|
20
21
|
export declare const graphNodeKindSchema: z.ZodEnum<{
|
|
21
22
|
start: "start";
|
|
23
|
+
join: "join";
|
|
22
24
|
ai: "ai";
|
|
23
25
|
human: "human";
|
|
24
26
|
program: "program";
|
|
@@ -131,6 +133,7 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
131
133
|
node_key: z.ZodOptional<z.ZodUUID>;
|
|
132
134
|
kind: z.ZodEnum<{
|
|
133
135
|
start: "start";
|
|
136
|
+
join: "join";
|
|
134
137
|
ai: "ai";
|
|
135
138
|
human: "human";
|
|
136
139
|
program: "program";
|
|
@@ -230,6 +233,7 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
230
233
|
node_key: z.ZodOptional<z.ZodUUID>;
|
|
231
234
|
kind: z.ZodEnum<{
|
|
232
235
|
start: "start";
|
|
236
|
+
join: "join";
|
|
233
237
|
ai: "ai";
|
|
234
238
|
human: "human";
|
|
235
239
|
program: "program";
|
package/dist/_schemas/graph.js
CHANGED
|
@@ -52,6 +52,7 @@ export const editableNodeKindSchema = nodeKindSchema.extract([
|
|
|
52
52
|
"human",
|
|
53
53
|
"program",
|
|
54
54
|
"wait",
|
|
55
|
+
"join",
|
|
55
56
|
]);
|
|
56
57
|
// グラフ一括保存で受け付ける kind(パレット作成の4種+構造ノード start/end)。
|
|
57
58
|
// start/end はパレットに出さないが、seed 済みなので全置換ボディには常に含まれる
|
|
@@ -63,6 +64,7 @@ export const graphNodeKindSchema = nodeKindSchema.extract([
|
|
|
63
64
|
"start",
|
|
64
65
|
"end",
|
|
65
66
|
"component",
|
|
67
|
+
"join",
|
|
66
68
|
]);
|
|
67
69
|
// Wait ノードの kind 別設定(node_waits の wait_type に対応する判別ユニオン)。
|
|
68
70
|
// duration=経過秒、cron=cron 式(次に来る該当時刻まで1回待つ)。
|
|
@@ -319,14 +321,16 @@ export const saveGraphBodySchema = z
|
|
|
319
321
|
outCount++;
|
|
320
322
|
});
|
|
321
323
|
// end は「in は1つ以上」(複数ルートを別々の in で集約する)。
|
|
322
|
-
// ai も1つ以上(in は自由ポート:追加・削除・key
|
|
324
|
+
// ai も1つ以上(in は自由ポート:追加・削除・key 自由)。join も1つ以上
|
|
325
|
+
// (待ち合わせる枝ごとに in)。他 kind は1つ固定。
|
|
323
326
|
// key の妥当性は kindPortsViolation が別途見る
|
|
324
|
-
const
|
|
327
|
+
const multiIn = node.kind === "end" || node.kind === "ai" || node.kind === "join";
|
|
328
|
+
const inCountOk = multiIn ? inCount >= 1 : inCount === 1;
|
|
325
329
|
if (!inCountOk) {
|
|
326
330
|
ctx.addIssue({
|
|
327
331
|
code: "custom",
|
|
328
332
|
path: ["nodes", i, "ports"],
|
|
329
|
-
message:
|
|
333
|
+
message: multiIn
|
|
330
334
|
? "in ポートが1つ以上必要です"
|
|
331
335
|
: "in ポートはちょうど1つ必要です",
|
|
332
336
|
});
|
package/dist/_schemas/node.d.ts
CHANGED
package/dist/_schemas/node.js
CHANGED
|
@@ -12,6 +12,9 @@ export const nodeKindSchema = z.enum([
|
|
|
12
12
|
// コンポーネント(kind='component' の loop を配置して呼ぶ。子 run・戻り値あり。
|
|
13
13
|
// docs/tasks/done/コンポーネント(ループの部品化と再利用).md)
|
|
14
14
|
"component",
|
|
15
|
+
// 合流(線のつながった入口が全部揃うまで待ち、key ごとにまとめて流す。
|
|
16
|
+
// docs/tasks/wip/合流ノード(並行分岐の待ち合わせ).md)
|
|
17
|
+
"join",
|
|
15
18
|
]);
|
|
16
19
|
// ポート key(input/output 用の参照キー)の規則(db.md §2)。
|
|
17
20
|
// name(ユーザーが見る表示名)は自由テキスト
|
|
@@ -165,6 +165,17 @@ export const kindPortsViolation = (kind, ports) => {
|
|
|
165
165
|
}
|
|
166
166
|
return null;
|
|
167
167
|
}
|
|
168
|
+
// join は out が main 1つ固定・in は1つ以上(待ち合わせる枝ごとに in。key 自由。
|
|
169
|
+
// 合流ノード(並行分岐の待ち合わせ).md)
|
|
170
|
+
if (kind === "join") {
|
|
171
|
+
const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key);
|
|
172
|
+
if (outKeys.length !== 1 ||
|
|
173
|
+
outKeys[0] !== "main" ||
|
|
174
|
+
!ports.some((p) => p.io === "in")) {
|
|
175
|
+
return `join のポート構成は in 1つ以上と out "main" ちょうど1つです`;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
168
179
|
if (kind === "human") {
|
|
169
180
|
const inKeys = ports.filter((p) => p.io === "in").map((p) => p.key);
|
|
170
181
|
const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key);
|
|
@@ -263,7 +274,8 @@ export const COPY_UPSTREAM_SCHEMA = "T";
|
|
|
263
274
|
// resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
|
|
264
275
|
// start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
|
|
265
276
|
// (run 入力契約=接続先の入力宣言。開始ノードの入力契約を接続先から導出.md)
|
|
266
|
-
|
|
277
|
+
// join も導出ノード(各 in は上流から、out main は線のつながった in を key ごとに持つ object)
|
|
278
|
+
const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"]);
|
|
267
279
|
// in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
|
|
268
280
|
const MIRROR_KINDS = new Set(["program", "ai"]);
|
|
269
281
|
const isDerivedPort = (kind, port) => DERIVED_KINDS.has(kind) &&
|
|
@@ -331,6 +343,30 @@ export const resolvePortSchemas = (nodes, connections, options) => {
|
|
|
331
343
|
}
|
|
332
344
|
continue;
|
|
333
345
|
}
|
|
346
|
+
// join:各 in を自分の上流から導出(end と同じ)。out main は線のつながった in を
|
|
347
|
+
// key ごとに持つ object(「両方」を1段入れて持つ。中身を平らに混ぜると key が
|
|
348
|
+
// ぶつかる)。待つのは線のつながった in だけなので、未接続の in は出口に出さない
|
|
349
|
+
if (node.kind === "join") {
|
|
350
|
+
const properties = {};
|
|
351
|
+
for (const port of node.ports) {
|
|
352
|
+
if (port.io !== "in")
|
|
353
|
+
continue;
|
|
354
|
+
const srcs = inbound.get(port.id) ?? [];
|
|
355
|
+
const schema = mergeInboundSchemas(srcs.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
|
|
356
|
+
assign(port.id, schema);
|
|
357
|
+
if (srcs.length > 0) {
|
|
358
|
+
properties[port.key] = { ...schema, title: port.name };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const outMain = node.ports.find((p) => p.io === "out" && p.key === "main");
|
|
362
|
+
const keys = Object.keys(properties);
|
|
363
|
+
if (outMain) {
|
|
364
|
+
assign(outMain.id, keys.length > 0
|
|
365
|
+
? { type: "object", properties, required: keys }
|
|
366
|
+
: UNDECLARED_SCHEMA);
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
334
370
|
const isMirror = MIRROR_KINDS.has(node.kind);
|
|
335
371
|
if (!DERIVED_KINDS.has(node.kind) && !isMirror)
|
|
336
372
|
continue;
|
package/docs/development.md
CHANGED
|
@@ -49,7 +49,9 @@ skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
|
|
|
49
49
|
- `config.json` — 正は `_schemas/ai-manifest.ts`。`prompt`(必須)・`model`・
|
|
50
50
|
`skills`(トップレベル `skills/` 配下のディレクトリ名)・`env`・`inputs` / `outputs`
|
|
51
51
|
- `model` が実行主体を決める:`claude-*` は Claude Code、`gpt-*` は Codex で走る
|
|
52
|
-
(選択肢は `AI_MODEL_GROUPS
|
|
52
|
+
(選択肢は `AI_MODEL_GROUPS`)。値は系列(`claude-opus`・`gpt-sol` 等。その系列の最新モデルで
|
|
53
|
+
走り、新モデルが出れば自動で切り替わる)か、固定 ID(`claude-opus-5` 等。そのバージョンで
|
|
54
|
+
走り続ける)。省略時は `claude-opus`。認証は repo の Actions secrets に置く:Claude Code は
|
|
53
55
|
`ANTHROPIC_API_KEY`(または `CLAUDE_CODE_OAUTH_TOKEN`)、Codex は明示的な `auth_mode` と対応する認証情報(下記「Codex の認証方式」参照)
|
|
54
56
|
(`env` に宣言しなくても渡される)
|
|
55
57
|
- `inputs` は1件以上・key 自由。値は `{ "schema": <JSON Schema>, "resume": true }` の形
|
|
@@ -91,7 +93,7 @@ Codex の AI 定義は `auth_mode` を必ず指定する。エディタの「認
|
|
|
91
93
|
```json
|
|
92
94
|
{
|
|
93
95
|
"prompt": "入力に返信してください",
|
|
94
|
-
"model": "gpt-
|
|
96
|
+
"model": "gpt-luna",
|
|
95
97
|
"auth_mode": "subscription",
|
|
96
98
|
"inputs": { "in": { "schema": {} } },
|
|
97
99
|
"outputs": { "main": { "type": "string" } }
|
package/package.json
CHANGED
|
@@ -73,7 +73,10 @@ repo との矛盾・使用中ノードの削除は 409。**graph PUT は冪等
|
|
|
73
73
|
|
|
74
74
|
## 手順
|
|
75
75
|
|
|
76
|
-
1. **設計**:要件から必要なノード(program / ai / human / wait)と流れを決め、ユーザーに一言で確認する。
|
|
76
|
+
1. **設計**:要件から必要なノード(program / ai / human / wait / join)と流れを決め、ユーザーに一言で確認する。
|
|
77
|
+
1つの出口から複数の線を引くと枝が並行に走る(fan-out)。枝の結果を待ち合わせるときは
|
|
78
|
+
合流(`join`)を置く:線のつながった入口が全部揃うまで待ち、`{ [入口の key]: 値 }` にまとめて
|
|
79
|
+
`main` から流す。並行した枝を合流させずに同じノード(待機など)へ戻すと、枝の数だけ走り続ける。
|
|
77
80
|
2. **repo 側の準備**:足りない program / ai は `nodes/program/<dir>/` / `nodes/ai/<dir>/` に作る
|
|
78
81
|
(config.json → `npx mawaru typegen` → main.ts 実装 → `npx mawaru validate`)。
|
|
79
82
|
**AI ノードの決定論的な処理は prompt ではなく `setup.sh` に書く**(下記)。
|
|
@@ -84,7 +87,8 @@ repo との矛盾・使用中ノードの削除は 409。**graph PUT は冪等
|
|
|
84
87
|
- node / port の `id` はクライアント採番(`uuidgen` などで UUID を作る)
|
|
85
88
|
- program / ai ノードの ports は repo の config.json の inputs / outputs をそのまま写す
|
|
86
89
|
(in ポート key は `in`、out ポートは outputs の各 key)
|
|
87
|
-
- kind 別の固定ポート構成(human / wait / ai)は `port-spec.ts` の `kindPortsViolation`
|
|
90
|
+
- kind 別の固定ポート構成(human / wait / ai / join)は `port-spec.ts` の `kindPortsViolation` に従う。
|
|
91
|
+
join は in を枝の数だけ(key 自由。例 `in_1`, `in_2`)+ out `main`。出口の型は自動で決まる
|
|
88
92
|
- ループ変数(refs=run 全体で読める key→値 の袋)が要るならトップレベルの `refs` に入れる。
|
|
89
93
|
ノード単位の宣言やバインドは無い。handler は実行時に `refs.json` で全量を受け取り、
|
|
90
94
|
`end` の `refs` パッチで値を更新できる(config.json の `refs` はループ定義からの転写=生成物)。
|