@mawaru/sdk 0.40.0 → 0.41.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 (56) hide show
  1. package/_schemas/graph.ts +0 -5
  2. package/_schemas/human-manifest.test.ts +39 -101
  3. package/_schemas/human-manifest.ts +25 -152
  4. package/_schemas/port-spec.test.ts +33 -92
  5. package/_schemas/port-spec.ts +35 -44
  6. package/dist/_cli/login.js +15 -1
  7. package/dist/_cli/previewApi.d.ts +1 -1
  8. package/dist/_cli/previewCommand.d.ts +0 -8
  9. package/dist/_cli/previewCommand.js +37 -95
  10. package/dist/_cli/previewFiles.d.ts +1 -2
  11. package/dist/_cli/previewFiles.js +18 -42
  12. package/dist/_schemas/graph.d.ts +0 -15
  13. package/dist/_schemas/graph.js +0 -5
  14. package/dist/_schemas/human-manifest.d.ts +6 -24
  15. package/dist/_schemas/human-manifest.js +24 -116
  16. package/dist/_schemas/port-spec.d.ts +1 -4
  17. package/dist/_schemas/port-spec.js +25 -34
  18. package/dist/cli.js +1 -2
  19. package/dist/typegen.d.ts +2 -2
  20. package/dist/typegen.js +21 -21
  21. package/dist/validate.js +9 -7
  22. package/dist/views/_humanProps.d.ts +10 -0
  23. package/dist/views/_humanProps.js +11 -0
  24. package/dist/views/{index.d.ts → components.d.ts} +0 -1
  25. package/dist/views/components.js +12 -0
  26. package/dist/views/host.d.ts +8 -3
  27. package/dist/views/host.js +5 -5
  28. package/dist/views/human.d.ts +49 -68
  29. package/dist/views/human.js +5 -6
  30. package/dist/views/protocol.d.ts +13 -3
  31. package/dist/views/protocol.js +2 -2
  32. package/dist/views/runtime.d.ts +2 -3
  33. package/dist/views/runtime.js +24 -24
  34. package/dist/views/sample.d.ts +22 -0
  35. package/dist/views/sample.js +38 -0
  36. package/docs/development.md +30 -31
  37. package/package.json +5 -1
  38. package/skills/create-loop/SKILL.md +3 -2
  39. package/templates/CLAUDE.md +2 -0
  40. package/views/_humanProps.ts +27 -0
  41. package/views/components.ts +16 -0
  42. package/views/host.ts +9 -5
  43. package/views/human.ts +71 -79
  44. package/views/protocol.ts +29 -6
  45. package/views/runtime.tsx +36 -45
  46. package/views/sample.tsx +94 -0
  47. package/dist/_cli/openBrowser.d.ts +0 -1
  48. package/dist/_cli/openBrowser.js +0 -15
  49. package/dist/views/_approval/ApprovalBar.d.ts +0 -20
  50. package/dist/views/_approval/ApprovalBar.js +0 -74
  51. package/dist/views/_approvalActions.d.ts +0 -18
  52. package/dist/views/_approvalActions.js +0 -10
  53. package/dist/views/index.js +0 -12
  54. package/views/_approval/ApprovalBar.tsx +0 -239
  55. package/views/_approvalActions.tsx +0 -31
  56. package/views/index.ts +0 -16
@@ -1,121 +1,29 @@
1
1
  import { z } from "zod";
2
- import { jsonSchemaSchema } from "./node.js";
3
- import { COPY_UPSTREAM_SCHEMA, instructionSchema, } from "./port-spec.js";
4
- // 宣言に書ける type。file は x-mawaru-type マーカー付きの object
5
- const DECLARED_TYPES = [
6
- "object",
7
- "array",
8
- "string",
9
- "number",
10
- "integer",
11
- "boolean",
12
- ];
13
- const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14
- // JSON Schema の形の崩れを場所つきで返す。接続の比較(schemaFit)が扱える形だけを通す:
15
- // 顧客 repo の宣言は mawaru のレビューを通らないので、壊れた型がノードに写る前に止める
16
- const schemaIssues = (schema, path) => {
17
- const issues = [];
18
- const { type, properties, required, items } = schema;
19
- if (Array.isArray(type)) {
20
- issues.push({
21
- path: [...path, "type"],
22
- message: "配列(nullable)は使えません。任意の値は required から外してください",
23
- });
24
- }
25
- else if (type !== undefined &&
26
- !DECLARED_TYPES.includes(type)) {
27
- issues.push({
28
- path: [...path, "type"],
29
- message: `type は ${DECLARED_TYPES.join(" / ")} のどれかにしてください(${String(type)})`,
30
- });
31
- }
32
- else if (type === undefined &&
33
- (properties !== undefined || items !== undefined)) {
34
- issues.push({
35
- path: [...path, "type"],
36
- message: "properties / items を書くときは type も書いてください",
37
- });
38
- }
39
- if (properties !== undefined) {
40
- if (!isPlainObject(properties)) {
41
- issues.push({
42
- path: [...path, "properties"],
43
- message: "properties はオブジェクトで書いてください",
44
- });
45
- }
46
- else {
47
- for (const [key, child] of Object.entries(properties)) {
48
- if (!isPlainObject(child)) {
49
- issues.push({
50
- path: [...path, "properties", key],
51
- message: "スキーマはオブジェクトで書いてください",
52
- });
53
- continue;
54
- }
55
- issues.push(...schemaIssues(child, [...path, "properties", key]));
56
- }
57
- }
58
- }
59
- if (required !== undefined &&
60
- !(Array.isArray(required) && required.every((r) => typeof r === "string"))) {
61
- issues.push({
62
- path: [...path, "required"],
63
- message: "required は文字列の配列で書いてください",
64
- });
65
- }
66
- if (items !== undefined) {
67
- if (!isPlainObject(items)) {
68
- issues.push({
69
- path: [...path, "items"],
70
- message: "items はスキーマ(オブジェクト)で書いてください",
71
- });
72
- }
73
- else {
74
- issues.push(...schemaIssues(items, [...path, "items"]));
75
- }
76
- }
77
- return issues;
78
- };
79
- // 宣言に書く1つの型(検査つき)
80
- const checkedSchema = jsonSchemaSchema.superRefine((schema, ctx) => {
81
- for (const issue of schemaIssues(schema, []))
82
- ctx.addIssue({ code: "custom", ...issue });
83
- });
84
- const declaredPortSchema = z.union([
85
- z.literal(COPY_UPSTREAM_SCHEMA),
86
- checkedSchema,
87
- ]);
88
- export const humanDeclarationSchema = z.object({
89
- in: declaredPortSchema,
90
- approve: declaredPortSchema,
91
- reject: checkedSchema,
92
- });
93
- // 標準ビュー(common/standard)の宣言:上流の型をそのまま受けて出し、差し戻しは標準の指示フォーム
94
- export const STANDARD_HUMAN_DECLARATION = {
95
- in: COPY_UPSTREAM_SCHEMA,
96
- approve: COPY_UPSTREAM_SCHEMA,
97
- reject: instructionSchema(),
98
- };
99
- // ---- 顧客 repo の nodes/human/<dir>/config.json ----
100
- // 形は AI の config.json に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
101
- // 口は in / approve / reject 固定。省略した口は STANDARD_HUMAN_DECLARATION と同じ扱い
2
+ import { jsonSchemaSchema, portKeySchema } from "./node.js";
3
+ import { kindPortsViolation } from "./port-spec.js";
4
+ // 顧客 repo の nodes/human/<dir>/config.json(human ノードの実装の入出力の宣言)。
5
+ // program / AI の config.json と同じく、エディタがディレクトリを選んだときに inputs / outputs から
6
+ // ports を起こし、ポートの編集は保存時にここへ書き戻される(実行時の正は ports テーブルのまま)。
7
+ // 形は AI に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
8
+ // 口の構成(in 1つ・approve / reject は必須・追加の出口は自由)は kind のポート構成の規則をそのまま使う
102
9
  export const humanManifestSchema = z
103
10
  .object({
104
- inputs: z
105
- .object({ in: z.object({ schema: checkedSchema }).strict().optional() })
106
- .strict()
107
- .optional(),
108
- outputs: z
109
- .object({
110
- approve: checkedSchema.optional(),
111
- reject: checkedSchema.optional(),
112
- })
113
- .strict()
114
- .optional(),
11
+ inputs: z.record(z.string(), z.object({ schema: jsonSchemaSchema })),
12
+ outputs: z.record(z.string(), jsonSchemaSchema),
13
+ // refs=ループ変数の定義の転写(program と同じ。エディタのグラフ保存が一方向に書く生成物)
14
+ refs: z.record(portKeySchema, jsonSchemaSchema).default({}),
115
15
  })
116
- .strict();
117
- export const humanDeclarationOfManifest = (config) => ({
118
- in: config.inputs?.in?.schema ?? COPY_UPSTREAM_SCHEMA,
119
- approve: config.outputs?.approve ?? COPY_UPSTREAM_SCHEMA,
120
- reject: config.outputs?.reject ?? instructionSchema(),
16
+ .superRefine((manifest, ctx) => {
17
+ const violation = kindPortsViolation("human", [
18
+ ...Object.keys(manifest.inputs).map((key) => ({
19
+ io: "in",
20
+ key,
21
+ })),
22
+ ...Object.keys(manifest.outputs).map((key) => ({
23
+ io: "out",
24
+ key,
25
+ })),
26
+ ]);
27
+ if (violation)
28
+ ctx.addIssue({ code: "custom", message: violation });
121
29
  });
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import type { HumanDeclaration } from "./human-manifest.js";
3
2
  import type { GraphPort, JsonSchema } from "./node.js";
4
3
  export declare const UNDECLARED_SCHEMA: JsonSchema;
5
4
  export declare const isUndeclaredSchema: (schema: JsonSchema) => boolean;
@@ -57,15 +56,13 @@ type PropagationNode = {
57
56
  kind: string;
58
57
  ports: GraphPort[];
59
58
  human?: {
60
- declaration?: HumanDeclaration;
59
+ human_dir?: string | null;
61
60
  } | null;
62
61
  };
63
62
  type PortLink = {
64
63
  from_port_id: string;
65
64
  to_port_id: string;
66
65
  };
67
- export declare const COPY_UPSTREAM_SCHEMA = "T";
68
- export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA;
69
66
  export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => Map<string, JsonSchema>;
70
67
  export declare const resolveFlexiblePorts: (nodes: PropagationNode[], connections: PortLink[]) => Set<string>;
71
68
  export declare const connectionAccepted: (from: JsonSchema, to: Pick<GraphPort, "schema" | "flexible">) => boolean;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  // ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
3
- // 型の決まり方は2分類:宣言(program / ai の in・out。human は実装(nodes/human/)の宣言を転写)/
3
+ // 型の決まり方は2分類:宣言(program / ai と、実装(nodes/human/<dir>/)を指す human の in・out)/
4
4
  // 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
5
5
  // mawaru がコードで型を固定するポートは無い
6
6
  // (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
@@ -222,9 +222,10 @@ export const kindPortsViolation = (kind, ports) => {
222
222
  // ---- 型伝播(導出ポートの解決) ----
223
223
  // 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
224
224
  // wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
225
- // human は実装(nodes/human/)の宣言を転写("T" は素通しと同じ挙動。reject もビューの宣言)。
226
- // 宣言 out(program / ai)は触らない。
227
- // program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
225
+ // human は実装(nodes/human/<dir>/)を指していれば program / ai と同じ宣言ノード、指していなければ
226
+ // (標準の承認画面)wait と同じ素通し(approve は in と同型、reject は標準の差し戻し instructionSchema)。
227
+ // 宣言 out(program / ai / 実装を指す human)は触らない。
228
+ // program / ai(と実装を指す human)の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
228
229
  // 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
229
230
  // config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
230
231
  // ---- 型としての同一性(schemaTypeKey) ----
@@ -273,9 +274,6 @@ export const mergeInboundSchemas = (schemas) => {
273
274
  // 流入の具体型が2種類以上あるか(=同じポートに違う型を流し込んでいる)
274
275
  export const hasInboundConflict = (schemas) => new Set(schemas.filter((s) => !isUndeclaredSchema(s)).map(schemaTypeKey))
275
276
  .size > 1;
276
- // 「上流の out をそのままコピーする」(in)/「in と同型」(approve)を表す指示。
277
- // 承認ビューのカタログ(@mawaru/common)の COPY_UPSTREAM と同じ値
278
- export const COPY_UPSTREAM_SCHEMA = "T";
279
277
  // end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
280
278
  // resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
281
279
  // start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
@@ -284,9 +282,14 @@ export const COPY_UPSTREAM_SCHEMA = "T";
284
282
  const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"]);
285
283
  // in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
286
284
  const MIRROR_KINDS = new Set(["program", "ai"]);
287
- const isDerivedPort = (kind, port) => DERIVED_KINDS.has(kind) &&
285
+ // 実装(nodes/human/<dir>/)を指す human は program / ai と同じ(in は鏡映・out は宣言。
286
+ // 型は config.json へ書き戻される)。指していない human は導出(標準の承認画面の素通し)
287
+ const isMirrorNode = (node) => MIRROR_KINDS.has(node.kind) ||
288
+ (node.kind === "human" && Boolean(node.human?.human_dir));
289
+ const isDerivedNode = (node) => DERIVED_KINDS.has(node.kind) && !isMirrorNode(node);
290
+ const isDerivedPort = (node, port) => isDerivedNode(node) &&
288
291
  (port.io === "in" ||
289
- (kind !== "human" && port.key === "main") ||
292
+ (node.kind !== "human" && port.key === "main") ||
290
293
  port.key === "approve" ||
291
294
  port.key === "reject");
292
295
  // port_id → 解決済みスキーマ。導出は接続を遡って固定点まで反復し、
@@ -300,7 +303,7 @@ export const resolvePortSchemas = (nodes, connections) => {
300
303
  for (const node of nodes) {
301
304
  for (const port of node.ports) {
302
305
  kindByPortId.set(port.id, node.kind);
303
- resolved.set(port.id, isDerivedPort(node.kind, port) ? UNDECLARED_SCHEMA : port.schema);
306
+ resolved.set(port.id, isDerivedPort(node, port) ? UNDECLARED_SCHEMA : port.schema);
304
307
  }
305
308
  }
306
309
  // in ポートへの流入(差し戻しの fan-in があり得る。1本のときだけ型を採用)
@@ -373,10 +376,10 @@ export const resolvePortSchemas = (nodes, connections) => {
373
376
  }
374
377
  continue;
375
378
  }
376
- const isMirror = MIRROR_KINDS.has(node.kind);
377
- if (!DERIVED_KINDS.has(node.kind) && !isMirror)
379
+ const isMirror = isMirrorNode(node);
380
+ if (!isDerivedNode(node) && !isMirror)
378
381
  continue;
379
- // 鏡映(program / ai):全ての in が対象。具体型が流れてきたときだけ上書きする
382
+ // 鏡映(program / ai / 実装を指す human):全ての in が対象。具体型が流れてきたときだけ上書きする
380
383
  // (宣言を未宣言で潰さない)。ai の in はどの口も対等(instruction の特別扱いは無い)
381
384
  if (isMirror) {
382
385
  for (const port of node.ports) {
@@ -392,34 +395,22 @@ export const resolvePortSchemas = (nodes, connections) => {
392
395
  }
393
396
  continue;
394
397
  }
395
- // 導出(wait / human):in は上流から
398
+ // 導出(wait / 実装を指さない human):in は上流から、out(main・approve)は in と同型。
399
+ // human の reject は標準の承認画面の差し戻し(指示+参考ファイル)。
400
+ // 上流の out は書き換えない(逆伝播すると fan-out 先の型割れが config.json へ書き戻され、
401
+ // 元の出力宣言を黙って壊す。ズレは接続の型エラーで見せる)
396
402
  const inPort = node.ports.find((p) => p.io === "in");
397
403
  if (!inPort)
398
404
  continue;
399
405
  const sources = inbound.get(inPort.id) ?? [];
400
406
  const inSchema = mergeInboundSchemas(sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
401
- // 実装(nodes/human/<dir>/)の宣言を human のポートへそのまま転写する(上流ノードの kind は見ない)。
402
- // "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
403
- // のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
404
- // 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
405
- // 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
406
- // ズレは接続の型エラーで見せてユーザーの [修正する] で直す
407
- const declared = node.kind === "human" ? node.human?.declaration : undefined;
408
- const effectiveIn = declared && declared.in !== COPY_UPSTREAM_SCHEMA
409
- ? declared.in
410
- : inSchema;
411
- assign(inPort.id, effectiveIn);
407
+ assign(inPort.id, inSchema);
412
408
  for (const port of node.ports) {
413
- if (port.io !== "out" || !isDerivedPort(node.kind, port))
409
+ if (port.io !== "out" || !isDerivedPort(node, port))
414
410
  continue;
415
- if (node.kind === "human" && port.key === "reject") {
416
- if (declared)
417
- assign(port.id, declared.reject);
418
- continue;
419
- }
420
- assign(port.id, declared && declared.approve !== COPY_UPSTREAM_SCHEMA
421
- ? declared.approve
422
- : effectiveIn);
411
+ assign(port.id, node.kind === "human" && port.key === "reject"
412
+ ? instructionSchema()
413
+ : inSchema);
423
414
  }
424
415
  }
425
416
  if (!changed)
package/dist/cli.js CHANGED
@@ -19,8 +19,7 @@ const USAGE = `使い方: mawaru <command> [引数]
19
19
  session resume <step URL>
20
20
  AI ノードのセッションを手元に復元して CLI(claude / codex)の resume で再開する
21
21
  preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]
22
- push せずに手元のファイルで確かめる(human:nodes/human/ の承認画面をブラウザで開く/
23
- slack:views/slack/ のビューで cases.ts のケースを Slack に送る)`;
22
+ views/ のビューを push せずに手元のファイルで確かめる(slack:cases.ts のケースを Slack に送る)`;
24
23
  const [command, ...rest] = process.argv.slice(2);
25
24
  // init / typegen / validate は repo ルートを任意で受ける(省略時はカレント)
26
25
  const root = rest[0] ?? process.cwd();
package/dist/typegen.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { type HumanDeclaration } from "./_schemas/human-manifest.js";
1
+ import { type HumanManifest } from "./_schemas/human-manifest.js";
2
2
  import type { JsonSchema } from "./_schemas/node.js";
3
3
  import { type ProgramManifest } from "./_schemas/program-manifest.js";
4
4
  export declare const CONFIG_HASH_PREFIX = "// mawaru:config-hash=";
5
5
  export declare const configHash: (rawConfig: string) => string;
6
6
  export declare const tsTypeFromJsonSchema: (schema: JsonSchema, indent?: number, fileAs?: string) => string;
7
7
  export declare const renderTypesFile: (manifest: ProgramManifest, rawConfig: string) => string;
8
- export declare const renderApprovalViewTypesFile: (declaration: HumanDeclaration, rawConfig: string) => string;
8
+ export declare const renderHumanTypesFile: (manifest: HumanManifest, rawConfig: string) => string;
9
9
  export type TypegenResult = {
10
10
  written: string[];
11
11
  errors: {
package/dist/typegen.js CHANGED
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { REFS_INPUT_KEY } from "./_schemas/graph.js";
5
- import { humanDeclarationOfManifest, humanManifestSchema, } from "./_schemas/human-manifest.js";
5
+ import { humanManifestSchema, } from "./_schemas/human-manifest.js";
6
6
  import { programManifestSchema, } from "./_schemas/program-manifest.js";
7
7
  // 生成ファイルに刻む config.json 内容のハッシュ。validate が drift(config を変えて
8
8
  // typegen し忘れ)の検出に使う
@@ -171,15 +171,18 @@ export const renderTypesFile = (manifest, rawConfig) => {
171
171
  "",
172
172
  ].join("\n");
173
173
  };
174
- // human ノードの実装(nodes/human/<dir>/config.json)の型。ビューの ctx の型(defineApprovalView<In>)と、
175
- // 承認バーで approve に流す値の型(<ApprovalBar<Approve> output={…} />)に使う。
174
+ // human ノードの実装(nodes/human/<dir>/config.json)の型。program と同じ Input / Outputs / Output / Refs を作り、
175
+ // defineHuman<Input, Output, Refs>(({ input, out, ctx }) => …) に使う。
176
176
  // file は参照(FileRef)のまま届く(program の handler と違って実体化しない)
177
- export const renderApprovalViewTypesFile = (declaration, rawConfig) => {
178
- const typeOf = (schema) => tsTypeFromJsonSchema(schema, 0, "FileRef");
179
- const inType = declaration.in === "T" ? "unknown" : typeOf(declaration.in);
180
- const approveType = declaration.approve === "T" ? "In" : typeOf(declaration.approve);
181
- const rejectType = typeOf(declaration.reject);
182
- const usesFileRef = [inType, approveType, rejectType].some((t) => t.includes("FileRef"));
177
+ export const renderHumanTypesFile = (manifest, rawConfig) => {
178
+ const inputType = tsTypeFromJsonSchema(manifest.inputs.in?.schema ?? {}, 0, "FileRef");
179
+ const outputEntries = Object.entries(manifest.outputs).map(([key, schema]) => ` ${propertyKey(key)}: ${tsTypeFromJsonSchema(schema, 1, "FileRef")}`);
180
+ // refs=ループ変数の袋(program と同じ。予約キー input は run の起動入力)
181
+ const refEntries = [
182
+ ` ${propertyKey(REFS_INPUT_KEY)}: unknown`,
183
+ ...Object.entries(manifest.refs).map(([key, schema]) => ` ${propertyKey(key)}: ${tsTypeFromJsonSchema(schema, 1, "FileRef")}`),
184
+ ];
185
+ const usesFileRef = [inputType, ...outputEntries, ...refEntries].some((t) => t.includes("FileRef"));
183
186
  return [
184
187
  "// このファイルは mawaru typegen が config.json から自動生成した(手で編集しない)",
185
188
  `${CONFIG_HASH_PREFIX}${configHash(rawConfig)}`,
@@ -187,18 +190,18 @@ export const renderApprovalViewTypesFile = (declaration, rawConfig) => {
187
190
  ...(usesFileRef
188
191
  ? ['import type { FileRef } from "@mawaru/sdk/human"', ""]
189
192
  : []),
190
- "// 承認待ちの値(ctx.output)。inputs.in を省略した(上流の型をそのまま受ける)なら unknown",
191
- `export type In = ${inType}`,
193
+ `export type Input = ${inputType}`,
194
+ "",
195
+ `export type Outputs = {\n${outputEntries.join("\n")}\n}`,
192
196
  "",
193
- "// 承認で approve に載せる値(ApprovalBar の output)。省略したなら In と同じ",
194
- `export type Approve = ${approveType}`,
197
+ "// out に渡す値:ちょうど1つの出口ポートのキーで渡す { <出口キー>: 値 }",
198
+ outputUnionType(Object.keys(manifest.outputs)),
195
199
  "",
196
- "// 差し戻しで reject に載せる値",
197
- `export type Reject = ${rejectType}`,
200
+ `export type Refs = {\n${refEntries.join("\n")}\n}`,
198
201
  "",
199
202
  ].join("\n");
200
203
  };
201
- // nodes/human/ 直下の各 human ノードの実装(`_` 始まりは部品の置き場なので除く)の config.json から types.d.ts を生成する
204
+ // nodes/human/ 直下の各 human ノードの実装の config.json から types.d.ts を生成する(program と同じ走査)
202
205
  const runHumanTypegen = (root) => {
203
206
  const viewsDir = join(root, "nodes", "human");
204
207
  const written = [];
@@ -206,11 +209,8 @@ const runHumanTypegen = (root) => {
206
209
  if (!existsSync(viewsDir))
207
210
  return { written, errors };
208
211
  for (const entry of readdirSync(viewsDir, { withFileTypes: true })) {
209
- if (!entry.isDirectory() ||
210
- entry.name.startsWith("_") ||
211
- entry.name.startsWith(".")) {
212
+ if (!entry.isDirectory())
212
213
  continue;
213
- }
214
214
  const dir = `nodes/human/${entry.name}`;
215
215
  const configPath = join(viewsDir, entry.name, "config.json");
216
216
  if (!existsSync(configPath)) {
@@ -235,7 +235,7 @@ const runHumanTypegen = (root) => {
235
235
  continue;
236
236
  }
237
237
  const typesPath = join(viewsDir, entry.name, "types.d.ts");
238
- writeFileSync(typesPath, renderApprovalViewTypesFile(humanDeclarationOfManifest(config.data), rawConfig));
238
+ writeFileSync(typesPath, renderHumanTypesFile(config.data, rawConfig));
239
239
  written.push(typesPath);
240
240
  }
241
241
  return { written, errors };
package/dist/validate.js CHANGED
@@ -102,19 +102,15 @@ const validateProgram = (programsDir, dir, root) => {
102
102
  checkTypegenDrift(programDir, rawConfig, path, issues);
103
103
  return issues;
104
104
  };
105
- // human ノードの実装(nodes/human/<dir>/):config.json(宣言の検査)・index.tsx・types.d.ts の drift。
106
- // `_` 始まりは部品の置き場なので見ない
105
+ // human ノードの実装(nodes/human/<dir>/):config.json・index.tsx・types.d.ts の drift(program と同じ検査)
107
106
  const validateHumans = (root) => {
108
107
  const issues = [];
109
108
  const viewsDir = join(root, "nodes", "human");
110
109
  if (!existsSync(viewsDir))
111
110
  return issues;
112
111
  for (const entry of readdirSync(viewsDir, { withFileTypes: true })) {
113
- if (!entry.isDirectory() ||
114
- entry.name.startsWith("_") ||
115
- entry.name.startsWith(".")) {
112
+ if (!entry.isDirectory())
116
113
  continue;
117
- }
118
114
  const viewDir = join(viewsDir, entry.name);
119
115
  const path = relative(root, viewDir);
120
116
  if (!existsSync(join(viewDir, "index.tsx"))) {
@@ -141,14 +137,20 @@ const validateHumans = (root) => {
141
137
  const config = humanManifestSchema.safeParse(parsed);
142
138
  if (!config.success) {
143
139
  for (const issue of config.error.issues) {
140
+ const at = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
144
141
  issues.push({
145
142
  level: "error",
146
143
  path: `${path}/config.json`,
147
- message: `${issue.path.join(".")}: ${issue.message}`,
144
+ message: `${at}${issue.message}`,
148
145
  });
149
146
  }
150
147
  continue;
151
148
  }
149
+ checkPortSchemas(Object.fromEntries(Object.entries(config.data.inputs).map(([key, value]) => [
150
+ key,
151
+ value.schema,
152
+ ])), "inputs", `${path}/config.json`, issues);
153
+ checkPortSchemas(config.data.outputs, "outputs", `${path}/config.json`, issues);
152
154
  if (existsSync(join(viewDir, "types.d.ts"))) {
153
155
  checkTypegenDrift(viewDir, rawConfig, path, issues);
154
156
  }
@@ -0,0 +1,10 @@
1
+ import type { FileRef } from "./_parts/fileRef.js";
2
+ import type { HumanProps } from "./human.js";
3
+ import type { HumanViewData } from "./protocol.js";
4
+ export type HumanOps = {
5
+ out: (output: Record<string, unknown>) => Promise<void>;
6
+ fetchFileUrl: (file: FileRef) => Promise<string>;
7
+ uploadFile: (file: File) => Promise<FileRef>;
8
+ writeText: (text: string) => Promise<void>;
9
+ };
10
+ export declare const humanPropsOf: (data: HumanViewData, ops: HumanOps) => HumanProps;
@@ -0,0 +1,11 @@
1
+ export const humanPropsOf = (data, ops) => ({
2
+ input: data.input,
3
+ out: ops.out,
4
+ ctx: {
5
+ input: { schema: data.inputSchema, fetchFileUrl: ops.fetchFileUrl },
6
+ output: { schema: data.outputSchema, uploadFile: ops.uploadFile },
7
+ clipboard: { writeText: ops.writeText },
8
+ refs: (data.refs ?? {}),
9
+ step: { isAssignee: data.isAssignee, editable: data.editable },
10
+ },
11
+ });
@@ -1,4 +1,3 @@
1
- export { useViewBridge, type ViewBridge } from "./_bridge.js";
2
1
  export { documentCardStyle } from "./_parts/DocumentCard.js";
3
2
  export { FilePreview } from "./_parts/FilePreview.js";
4
3
  export { type FileRef, isFileRef } from "./_parts/fileRef.js";
@@ -0,0 +1,12 @@
1
+ // 共通の部品(`@mawaru/sdk/components`)。human ノードの約束(`@mawaru/sdk/human`)には依存しない。
2
+ // mawaru の画面と顧客 repo の human ノードの実装が同じ部品を使う(mawaru が顧客 repo の実装をまとめるとき、
3
+ // mawaru の今の版のこの部品を同梱する)。ファイルの表示・アップロードは、mawaru の画面ではホストの口、
4
+ // sandbox iframe の中では mawaru の承認画面への頼みごととして裏で差し込まれる(_bridge.tsx)。
5
+ // **公開 API**:部品の props は後方互換でしか変えない
6
+ export { documentCardStyle } from "./_parts/DocumentCard.js";
7
+ export { FilePreview } from "./_parts/FilePreview.js";
8
+ export { isFileRef } from "./_parts/fileRef.js";
9
+ export { isUrl, Linkify, linkify } from "./_parts/Linkify.js";
10
+ export { isLongText, Markdown, useAutoGrow } from "./_parts/Markdown.js";
11
+ export { asFormSchema, SchemaForm, } from "./_parts/SchemaForm.js";
12
+ export { NARROW_WIDTH, useNarrow } from "./_parts/useNarrow.js";
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- export { ApprovalActionsProvider } from "./_approvalActions.js";
3
- export { ViewBridgeProvider } from "./_bridge.js";
2
+ export { useViewBridge, type ViewBridge, ViewBridgeProvider, } from "./_bridge.js";
3
+ export { type HumanOps, humanPropsOf } from "./_humanProps.js";
4
4
  export * from "./protocol.js";
5
5
  export declare const viewToHostSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6
6
  tag: z.ZodLiteral<"mawaru:view">;
@@ -13,6 +13,11 @@ export declare const viewToHostSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
13
13
  tag: z.ZodLiteral<"mawaru:view">;
14
14
  type: z.ZodLiteral<"request">;
15
15
  id: z.ZodNumber;
16
- method: z.ZodString;
16
+ method: z.ZodEnum<{
17
+ out: "out";
18
+ fetchFileUrl: "fetchFileUrl";
19
+ uploadFile: "uploadFile";
20
+ writeText: "writeText";
21
+ }>;
17
22
  args: z.ZodUnknown;
18
23
  }, z.core.$strip>], "type">;
@@ -1,10 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { VIEW_MESSAGE_TAG } from "./protocol.js";
3
- export { ApprovalActionsProvider } from "./_approvalActions.js";
4
3
  // ホスト(mawaru の画面)だけが使うもの(公開 API ではない):sandbox iframe からのメッセージを確かめる形
5
- // (protocol.ts の型と同じ。iframe の中は信用できないコードなので、形を確かめてから使う)と、
6
- // ビューに頼む口を差す提供部品
7
- export { ViewBridgeProvider } from "./_bridge.js";
4
+ // (protocol.ts の型と同じ。iframe の中は信用できないコードなので、形を確かめてから使う)、
5
+ // 部品が裏で使う口を差す提供部品、human ノードの実装に渡す props の組み立て(標準の承認画面も同じものを使う)
6
+ export { useViewBridge, ViewBridgeProvider, } from "./_bridge.js";
7
+ export { humanPropsOf } from "./_humanProps.js";
8
8
  export * from "./protocol.js";
9
9
  const tag = z.literal(VIEW_MESSAGE_TAG);
10
10
  export const viewToHostSchema = z.discriminatedUnion("type", [
@@ -14,7 +14,7 @@ export const viewToHostSchema = z.discriminatedUnion("type", [
14
14
  tag,
15
15
  type: z.literal("request"),
16
16
  id: z.number().int(),
17
- method: z.string(),
17
+ method: z.enum(["out", "fetchFileUrl", "uploadFile", "writeText"]),
18
18
  args: z.unknown(),
19
19
  }),
20
20
  ]);