@mawaru/sdk 0.5.0 → 0.9.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.
@@ -23,6 +23,7 @@ export declare const graphNodeKindSchema: z.ZodEnum<{
23
23
  wait: "wait";
24
24
  start: "start";
25
25
  end: "end";
26
+ component: "component";
26
27
  }>;
27
28
  export type GraphNodeKind = z.infer<typeof graphNodeKindSchema>;
28
29
  export declare const waitConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -46,7 +47,12 @@ export declare const aiConfigSchema: z.ZodObject<{
46
47
  ref: z.ZodOptional<z.ZodString>;
47
48
  }, z.core.$strip>;
48
49
  export type AiConfig = z.infer<typeof aiConfigSchema>;
50
+ export declare const RUN_STARTER = "run_starter";
51
+ export declare const assignMemberSchema: z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>;
52
+ export type AssignMember = z.infer<typeof assignMemberSchema>;
49
53
  export declare const humanConfigSchema: z.ZodObject<{
54
+ view: z.ZodOptional<z.ZodString>;
55
+ view_config: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
50
56
  assignment: z.ZodObject<{
51
57
  strategy: z.ZodDefault<z.ZodEnum<{
52
58
  fixed: "fixed";
@@ -56,10 +62,14 @@ export declare const humanConfigSchema: z.ZodObject<{
56
62
  any: "any";
57
63
  all: "all";
58
64
  }>>;
59
- units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID>>>;
65
+ units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
60
66
  }, z.core.$strip>;
61
67
  }, z.core.$strip>;
62
68
  export type HumanConfig = z.infer<typeof humanConfigSchema>;
69
+ export declare const componentConfigSchema: z.ZodObject<{
70
+ component_loop_id: z.ZodUUID;
71
+ }, z.core.$strip>;
72
+ export type ComponentConfig = z.infer<typeof componentConfigSchema>;
63
73
  export declare const graphNodeHookSchema: z.ZodObject<{
64
74
  hook_dir: z.ZodString;
65
75
  ref: z.ZodOptional<z.ZodString>;
@@ -77,6 +87,7 @@ export declare const graphNodeSchema: z.ZodObject<{
77
87
  wait: "wait";
78
88
  start: "start";
79
89
  end: "end";
90
+ component: "component";
80
91
  }>;
81
92
  name: z.ZodString;
82
93
  ports: z.ZodArray<z.ZodObject<{
@@ -124,6 +135,8 @@ export declare const graphNodeSchema: z.ZodObject<{
124
135
  ref: z.ZodOptional<z.ZodString>;
125
136
  }, z.core.$strip>>;
126
137
  human: z.ZodOptional<z.ZodObject<{
138
+ view: z.ZodOptional<z.ZodString>;
139
+ view_config: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
127
140
  assignment: z.ZodObject<{
128
141
  strategy: z.ZodDefault<z.ZodEnum<{
129
142
  fixed: "fixed";
@@ -133,9 +146,12 @@ export declare const graphNodeSchema: z.ZodObject<{
133
146
  any: "any";
134
147
  all: "all";
135
148
  }>>;
136
- units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID>>>;
149
+ units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
137
150
  }, z.core.$strip>;
138
151
  }, z.core.$strip>>;
152
+ component: z.ZodOptional<z.ZodObject<{
153
+ component_loop_id: z.ZodUUID;
154
+ }, z.core.$strip>>;
139
155
  }, z.core.$strip>;
140
156
  export type GraphNode = z.infer<typeof graphNodeSchema>;
141
157
  export declare const graphConnectionSchema: z.ZodObject<{
@@ -153,6 +169,7 @@ export declare const saveGraphBodySchema: z.ZodObject<{
153
169
  wait: "wait";
154
170
  start: "start";
155
171
  end: "end";
172
+ component: "component";
156
173
  }>;
157
174
  name: z.ZodString;
158
175
  ports: z.ZodArray<z.ZodObject<{
@@ -200,6 +217,8 @@ export declare const saveGraphBodySchema: z.ZodObject<{
200
217
  ref: z.ZodOptional<z.ZodString>;
201
218
  }, z.core.$strip>>;
202
219
  human: z.ZodOptional<z.ZodObject<{
220
+ view: z.ZodOptional<z.ZodString>;
221
+ view_config: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
203
222
  assignment: z.ZodObject<{
204
223
  strategy: z.ZodDefault<z.ZodEnum<{
205
224
  fixed: "fixed";
@@ -209,9 +228,12 @@ export declare const saveGraphBodySchema: z.ZodObject<{
209
228
  any: "any";
210
229
  all: "all";
211
230
  }>>;
212
- units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID>>>;
231
+ units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
213
232
  }, z.core.$strip>;
214
233
  }, z.core.$strip>>;
234
+ component: z.ZodOptional<z.ZodObject<{
235
+ component_loop_id: z.ZodUUID;
236
+ }, z.core.$strip>>;
215
237
  }, z.core.$strip>>;
216
238
  connections: z.ZodArray<z.ZodObject<{
217
239
  from_port_id: z.ZodUUID;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { graphPortSchema, jsonSchemaSchema, nodeKindSchema, portKeySchema, } from "./node.js";
3
- import { isAiEnvelopeSchema, isSchemaSubset, kindPortsViolation, resolvePortSchemas, } from "./port-spec.js";
3
+ import { AI_INSTRUCTION_PORT_KEY, hasInboundConflict, isAiEnvelopeSchema, isSchemaSubset, kindPortsViolation, resolvePortSchemas, UNDECLARED_SCHEMA, } from "./port-spec.js";
4
4
  // refs=ループ変数の袋(key→任意の JSON 値。上流出力の参照(refs).md)。
5
5
  // run 開始時に定義から複製され、全 step に全量差し込まれる。
6
6
  // key はポート key と同じ規則(^[a-z][a-z0-9_]*$)。実行系ボディ
@@ -50,6 +50,7 @@ export const graphNodeKindSchema = nodeKindSchema.extract([
50
50
  "wait",
51
51
  "start",
52
52
  "end",
53
+ "component",
53
54
  ]);
54
55
  // Wait ノードの kind 別設定(node_waits の wait_type に対応する判別ユニオン)。
55
56
  // duration=経過秒、until=絶対時刻(ISO)、cron=cron 式(次回発火まで1回待つ)。
@@ -90,18 +91,29 @@ export const aiConfigSchema = z.object({
90
91
  .refine((v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..", "ai_dir は nodes/ai/ 直下のディレクトリ名で指定してください"),
91
92
  ref: z.string().min(1).optional(),
92
93
  });
94
+ // 担当ユニットのメンバー:user_id の名指し、または動的な担当「ループを実行した人」
95
+ // (run の起動者。実行時に runs.start_by で解決し、解決できなければその1人だけ落ちる。
96
+ // docs/tasks/wip/Human担当に「ループを実行した人」を追加する.md)
97
+ export const RUN_STARTER = "run_starter";
98
+ export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)]);
93
99
  // Human ノードの kind 別設定(node_humans + node_human_assignees に対応)。
94
100
  // 担当 = ユニット(1人以上のメンバー)の順序付きリスト + 承認モード + 選出戦略。
95
- // units[i] は user_id の配列(1人=個人・複数=グループ)。units 空 = tenant の誰でも。
101
+ // units[i] はメンバーの配列(1人=個人・複数=グループ)。units 空 = tenant の誰でも。
96
102
  // approval_mode はノード単位(any=誰か1人 / all=全員。差し戻しは1人で成立=veto)。
97
103
  // メンバーが tenant の membership であることの検証は保存 API 側で行う
98
104
  // (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
99
105
  export const humanConfigSchema = z.object({
106
+ // 承認 UI(承認ビュー)の識別子。program_dir / ai_dir と同じ「不透明なキー」で、
107
+ // どのキーが実在するか・そのテナントが選べるかは保存時に mawaru 側が検証する
108
+ // (カタログは非公開。@mawaru/common の approval-view.ts)。省略時は標準ビュー
109
+ view: z.string().min(1).optional(),
110
+ // ビューに渡すパラメータ(列・ラベル・閾値など)。形はビューごとに違うので任意の JSON
111
+ view_config: z.unknown().nullish(),
100
112
  assignment: z
101
113
  .object({
102
114
  strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
103
115
  approval_mode: z.enum(["any", "all"]).default("any"),
104
- units: z.array(z.array(z.uuid()).min(1)).default([]),
116
+ units: z.array(z.array(assignMemberSchema).min(1)).default([]),
105
117
  })
106
118
  .superRefine((assignment, ctx) => {
107
119
  if (assignment.strategy === "fixed" && assignment.units.length > 1) {
@@ -119,18 +131,34 @@ export const humanConfigSchema = z.object({
119
131
  message: "round_robin にはユニットが2つ以上必要です",
120
132
  });
121
133
  }
134
+ // 交代制と「ループを実行した人」は排他:どの回に誰が担当かが起動者と交代位置の
135
+ // 掛け算になって読めなくなるため、設定できる形の方を減らす
136
+ if (assignment.strategy === "round_robin" &&
137
+ assignment.units.some((unit) => unit.includes(RUN_STARTER))) {
138
+ ctx.addIssue({
139
+ code: "custom",
140
+ path: ["units"],
141
+ message: "round_robin の担当に「ループを実行した人」は入れられません",
142
+ });
143
+ }
122
144
  // ユニット内の重複は拒否(またぐ重複は「Aは毎回・相手が交代」の形があるので許す)
123
145
  assignment.units.forEach((unit, i) => {
124
146
  if (new Set(unit).size !== unit.length) {
125
147
  ctx.addIssue({
126
148
  code: "custom",
127
149
  path: ["units", i],
128
- message: "ユニット内で user_id が重複しています",
150
+ message: "ユニット内で担当が重複しています",
129
151
  });
130
152
  }
131
153
  });
132
154
  }),
133
155
  });
156
+ // コンポーネントノードの kind 別設定(node_components に対応)。呼ぶ先は
157
+ // kind='component' の loop。同 tenant・kind の検証と自 loop 参照の禁止は保存 API 側で行う
158
+ // (docs/tasks/wip/コンポーネント(ループの部品化と再利用).md)
159
+ export const componentConfigSchema = z.object({
160
+ component_loop_id: z.uuid(),
161
+ });
134
162
  // ノードにアタッチされた IOHook(node_hooks に対応。アタッチできるのは program ノードのみ)。
135
163
  // on_input / on_output / on_signal はアタッチ時に repo の config.json(hookManifestSchema)から
136
164
  // エディタが焼き込むスナップショット。ref 省略時はデフォルトブランチ
@@ -161,6 +189,7 @@ export const graphNodeSchema = z.object({
161
189
  program: programConfigSchema.optional(),
162
190
  ai: aiConfigSchema.optional(),
163
191
  human: humanConfigSchema.optional(),
192
+ component: componentConfigSchema.optional(),
164
193
  });
165
194
  export const graphConnectionSchema = z.object({
166
195
  from_port_id: z.uuid(),
@@ -228,15 +257,18 @@ export const saveGraphBodySchema = z
228
257
  else
229
258
  outCount++;
230
259
  });
231
- // end だけ「in は1つ以上」(複数ルートを別々の in で集約する)。
232
- // start・他 kind は従来どおり1つ固定
233
- if (node.kind === "end" ? inCount < 1 : inCount !== 1) {
260
+ // end は「in は1つ以上」(複数ルートを別々の in で集約する)。
261
+ // ai は2つ(上流データの in + 差し戻しの instruction)。他 kind 1つ固定。
262
+ // key の妥当性は kindPortsViolation が別途見る
263
+ const expectIn = node.kind === "ai" ? 2 : 1;
264
+ const inCountOk = node.kind === "end" ? inCount >= 1 : inCount === expectIn;
265
+ if (!inCountOk) {
234
266
  ctx.addIssue({
235
267
  code: "custom",
236
268
  path: ["nodes", i, "ports"],
237
269
  message: node.kind === "end"
238
270
  ? "in ポートが1つ以上必要です"
239
- : "in ポートはちょうど1つ必要です",
271
+ : `in ポートはちょうど${expectIn}つ必要です`,
240
272
  });
241
273
  }
242
274
  if (outCount < 1) {
@@ -266,6 +298,15 @@ export const saveGraphBodySchema = z
266
298
  }
267
299
  hookDirs.add(hook.hook_dir);
268
300
  });
301
+ // component は参照先が無いと成立しない(他 kind の設定はデフォルトで補えるが
302
+ // component_loop_id は補えない)
303
+ if (node.kind === "component" && node.component === undefined) {
304
+ ctx.addIssue({
305
+ code: "custom",
306
+ path: ["nodes", i, "component"],
307
+ message: "component ノードには component_loop_id が必要です",
308
+ });
309
+ }
269
310
  // kind 別のポート構成(program 以外は数・key・io 固定。ポートのkind別仕様.md)
270
311
  const violation = kindPortsViolation(node.kind, node.ports);
271
312
  if (violation) {
@@ -295,7 +336,7 @@ export const saveGraphBodySchema = z
295
336
  const usedPairs = new Set();
296
337
  graph.connections.forEach((conn, i) => {
297
338
  const from = portById.get(conn.from_port_id);
298
- if (!from || from.io !== "out") {
339
+ if (from?.io !== "out") {
299
340
  ctx.addIssue({
300
341
  code: "custom",
301
342
  path: ["connections", i, "from_port_id"],
@@ -303,7 +344,7 @@ export const saveGraphBodySchema = z
303
344
  });
304
345
  }
305
346
  const to = portById.get(conn.to_port_id);
306
- if (!to || to.io !== "in") {
347
+ if (to?.io !== "in") {
307
348
  ctx.addIssue({
308
349
  code: "custom",
309
350
  path: ["connections", i, "to_port_id"],
@@ -323,6 +364,29 @@ export const saveGraphBodySchema = z
323
364
  // 型の接続バリデーション(ポートのkind別仕様.md §接続バリデーション)。
324
365
  // 導出ポートは接続から型を解決した上で判定する。ai の in・wait の in は常に許可
325
366
  const resolved = resolvePortSchemas(graph.nodes, graph.connections);
367
+ // fan-in(同じ in ポートへの流入が2本以上)は同じ型でなければならない。
368
+ // 型が割れていると伝播が未宣言に落ちて下流の型チェックが効かなくなる
369
+ // (docs/tasks/wip/ポートスキーマのany撲滅.md)
370
+ const inboundByPort = new Map();
371
+ for (const conn of graph.connections) {
372
+ const list = inboundByPort.get(conn.to_port_id) ?? [];
373
+ list.push(conn.from_port_id);
374
+ inboundByPort.set(conn.to_port_id, list);
375
+ }
376
+ for (const [toPortId, fromPortIds] of inboundByPort) {
377
+ if (fromPortIds.length < 2)
378
+ continue;
379
+ const schemas = fromPortIds.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA);
380
+ if (!hasInboundConflict(schemas))
381
+ continue;
382
+ const to = portById.get(toPortId);
383
+ const toNode = nodeByPortId.get(toPortId);
384
+ ctx.addIssue({
385
+ code: "custom",
386
+ path: ["connections"],
387
+ message: `同じ入力ポート(${toNode?.name ?? "?"}.${to?.key ?? "?"})に異なる型を接続できません`,
388
+ });
389
+ }
326
390
  graph.connections.forEach((conn, i) => {
327
391
  const from = portById.get(conn.from_port_id);
328
392
  const to = portById.get(conn.to_port_id);
@@ -345,21 +409,34 @@ export const saveGraphBodySchema = z
345
409
  message: "終了ノードからは接続できません",
346
410
  });
347
411
  }
348
- // human の reject の接続先は ai ノードのみ(kind 制約)
412
+ // human の reject の接続先は AI instruction ポートのみ。
413
+ // 差し戻しは「元のセッションに指示を与える」操作で、上流データの口(in)とは
414
+ // 意味が違う(docs/tasks/wip/ポートスキーマのany撲滅.md)
349
415
  if (fromNode.kind === "human" &&
350
416
  from.key === "reject" &&
351
- toNode.kind !== "ai") {
417
+ !(toNode.kind === "ai" && to.key === AI_INSTRUCTION_PORT_KEY)) {
352
418
  ctx.addIssue({
353
419
  code: "custom",
354
420
  path: ["connections", i, "to_port_id"],
355
- message: "差し戻し(reject)の接続先は AI ノードのみです",
421
+ message: "差し戻し(reject)の接続先は AI ノードの instruction ポートのみです",
422
+ });
423
+ }
424
+ // instruction ポートに繋げるのは human の reject だけ(データを流し込ませない)
425
+ if (toNode.kind === "ai" &&
426
+ to.key === AI_INSTRUCTION_PORT_KEY &&
427
+ !(fromNode.kind === "human" && from.key === "reject")) {
428
+ ctx.addIssue({
429
+ code: "custom",
430
+ path: ["connections", i, "to_port_id"],
431
+ message: "instruction ポートに接続できるのは human の差し戻し(reject)だけです",
356
432
  });
357
433
  }
358
434
  const fromSchema = resolved.get(conn.from_port_id) ?? from.schema;
359
435
  // human の in はどの型でも接続できる(上流が AI なら封筒 { data, description }、
360
436
  // それ以外は input 全体を data として表示する。型制約は課さない)
361
437
  // program の in への接続は 伝播型 ⊆ 宣言スキーマ。in は接続鏡映(sticky)なので
362
- // 具体型の接続では自明に通り、接続元 any × 宣言維持のときだけ従来の意味を持つ
438
+ // 具体型の接続では自明に通り、接続元が未宣言 × 宣言維持のときだけ従来の意味を持つ
439
+ // (未宣言は unknown=判断できないので通す。作りかけのグラフを保存できなくしない)
363
440
  const toSchema = resolved.get(conn.to_port_id) ?? to.schema;
364
441
  if (toNode.kind === "program" && !isSchemaSubset(fromSchema, toSchema)) {
365
442
  ctx.addIssue({
@@ -368,5 +445,14 @@ export const saveGraphBodySchema = z
368
445
  message: "接続元の型が program の入力スキーマに適合しません(伝播型 ⊆ 宣言スキーマ)",
369
446
  });
370
447
  }
448
+ // component の in は子 loop の契約(焼き込み済み宣言)。program と同じ包含チェック
449
+ if (toNode.kind === "component" &&
450
+ !isSchemaSubset(fromSchema, toSchema)) {
451
+ ctx.addIssue({
452
+ code: "custom",
453
+ path: ["connections", i, "to_port_id"],
454
+ message: "接続元の型がコンポーネントの入力契約に適合しません(伝播型 ⊆ 契約)",
455
+ });
456
+ }
371
457
  });
372
458
  });
@@ -7,6 +7,7 @@ export declare const nodeKindSchema: z.ZodEnum<{
7
7
  wait: "wait";
8
8
  start: "start";
9
9
  end: "end";
10
+ component: "component";
10
11
  }>;
11
12
  export type NodeKind = z.infer<typeof nodeKindSchema>;
12
13
  export declare const PORT_KEY_PATTERN: RegExp;
@@ -9,6 +9,9 @@ export const nodeKindSchema = z.enum([
9
9
  // docs/tasks/wip/開始・終了ノードの定義.md)
10
10
  "start",
11
11
  "end",
12
+ // コンポーネント(kind='component' の loop を配置して呼ぶ。子 run・戻り値あり。
13
+ // docs/tasks/wip/コンポーネント(ループの部品化と再利用).md)
14
+ "component",
12
15
  ]);
13
16
  // ポート key(input/output 用の参照キー)の規則(db.md §2)。
14
17
  // name(ユーザーが見る表示名)は自由テキスト
@@ -1,11 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import type { GraphPort, JsonSchema } from "./node.js";
3
- export declare const ANY_SCHEMA: JsonSchema;
4
- export declare const isAnySchema: (schema: JsonSchema) => boolean;
3
+ export declare const UNDECLARED_SCHEMA: JsonSchema;
4
+ export declare const isUndeclaredSchema: (schema: JsonSchema) => boolean;
5
5
  export declare const FILE_SCHEMA_MARKER = "x-mawaru-type";
6
6
  export declare const FILE_SCHEMA_TYPE = "file";
7
7
  export declare const fileSchema: () => JsonSchema;
8
8
  export declare const isFileSchema: (schema: JsonSchema) => boolean;
9
+ export declare const AI_INSTRUCTION_PORT_KEY = "instruction";
10
+ export declare const instructionSchema: () => JsonSchema;
9
11
  export declare const LIST_COLUMN_TYPES: readonly ["string", "number", "boolean", "date"];
10
12
  export declare const listColumnTypeSchema: z.ZodEnum<{
11
13
  string: "string";
@@ -46,7 +48,11 @@ export declare const dataFormatOf: (schema: JsonSchema) => DataFormat | null;
46
48
  export declare const envelopeSchemaOf: (dataSchema: JsonSchema) => JsonSchema;
47
49
  export declare const envelopeDataSchemaOf: (schema: JsonSchema) => JsonSchema | null;
48
50
  export declare const isAiEnvelopeSchema: (schema: JsonSchema) => boolean;
51
+ export declare const fixedInKeysOf: (kind: string) => string[] | null;
52
+ export declare const manifestInKeysViolation: (kind: string, keys: string[]) => string | null;
49
53
  export declare const kindPortsViolation: (kind: string, ports: Pick<GraphPort, "io" | "key">[]) => string | null;
54
+ export declare const mergeInboundSchemas: (schemas: JsonSchema[]) => JsonSchema;
55
+ export declare const hasInboundConflict: (schemas: JsonSchema[]) => boolean;
50
56
  type PropagationNode = {
51
57
  id: string;
52
58
  kind: string;
@@ -58,5 +64,7 @@ type PortLink = {
58
64
  };
59
65
  export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => Map<string, JsonSchema>;
60
66
  export declare const propagateDerivedPorts: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => N[];
67
+ export type SchemaFit = "ok" | "mismatch" | "unknown";
68
+ export declare const schemaFit: (sub: JsonSchema, sup: JsonSchema) => SchemaFit;
61
69
  export declare const isSchemaSubset: (sub: JsonSchema, sup: JsonSchema) => boolean;
62
70
  export {};