@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,73 +1,54 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { JsonSchema } from "../_schemas/node.js";
3
- /** 承認画面の局面:承認待ち/承認済み/差し戻し後(AI 再実行待ち)/失敗/キャンセル */
4
- export type ApprovalPhase = "waiting" | "approved" | "rejected" | "failed" | "cancelled";
5
- /**
6
- * 承認バーの材料。部品の `ApprovalBar` はこれをそのまま使う。承認バーを自分で書くときに読む。
7
- */
8
- export type ApprovalDecisionContext = {
9
- /** 見ている人が今決定できる(担当で承認待ち)。全員の承認が要るノードでも true(値を直せるかは `editable`) */
10
- canDecide: boolean;
11
- /** 承認したあとに流れる先のノード名(つながっていなければ null) */
12
- approveTargetName: string | null;
13
- /** 差し戻し先のノード名(無ければ差し戻せない) */
14
- rejectTargetName: string | null;
15
- /** 承認・差し戻し以外の出口。`needsInput` は、今の値の型が出口と合わず、値を入れるダイアログが要るもの */
16
- exits: {
17
- key: string;
18
- name: string;
19
- needsInput: boolean;
20
- }[];
21
- /** 担当でない人が開いたときの、担当の名前(担当なら null) */
22
- otherAssignees: string[] | null;
23
- /** 全員の承認が要るときの進み具合(`approvedByMe` は自分がもう承認したか)。要らなければ null */
24
- progress: {
25
- done: number;
26
- total: number;
27
- approvedByMe: boolean;
28
- } | null;
29
- /** 失敗した step の理由(失敗していなければ null) */
30
- failedReason: string | null;
31
- };
32
- /** mawaru から承認ビューへ渡す材料 */
33
- export type ApprovalViewContext<TIn = unknown> = {
34
- /** 届いた値(step の input そのもの)。ビューで直した値はビューの state に持つ */
35
- output: TIn;
36
- /** `output` の型= human の in ポートの型(ビューの宣言が写っている)。`SchemaForm` に渡すとフォームになる */
37
- outputSchema: JsonSchema | null;
38
- /** ループ変数(この step が始まった時点の写し)。予約キー `input` が run の起動入力 */
39
- refs: unknown;
40
- /** この human ノードの名前 */
41
- nodeName: string;
42
- /** 局面 */
43
- phase: ApprovalPhase;
44
- /** 値を直してよい(承認待ちで、見ている人が担当。全員の承認が要るノードでは false) */
45
- editable: boolean;
46
- /** 承認バーの材料 */
47
- decision: ApprovalDecisionContext;
3
+ import type { FileRef } from "./_parts/fileRef.js";
4
+ export type HumanProps<TInput = unknown, TOutput = Record<string, unknown>, TRefs = Record<string, unknown>> = {
5
+ /** 届いた値(in ポートの値= step の input) */
6
+ input: TInput;
7
+ /**
8
+ * 人の操作を mawaru に渡す。`{ <出口ポートのkey>: <データ> }`(program の run の戻り値と同じ1キーのオブジェクト)。
9
+ * 人がボタンを押した直後の呼び出しだけを受け付ける(開いただけで決定する画面は書けない)。
10
+ * 送れたら承認画面は run の画面へ戻る。断られたら(担当でない・もう決まっている など)理由つきで失敗する
11
+ */
12
+ out: (output: TOutput) => Promise<void>;
13
+ /** input 以外の材料(program の run の ctx と同じ位置づけ) */
14
+ ctx: {
15
+ input: {
16
+ /** input の型(in ポートの JSON Schema) */
17
+ schema: JsonSchema;
18
+ /** input に含まれるファイルを表示する URL を取る(sandbox の中からは mawaru のファイルを直接読めない) */
19
+ fetchFileUrl: (file: FileRef) => Promise<string>;
20
+ };
21
+ output: {
22
+ /** 出口ごとの型(out ポートの key → JSON Schema。approve・reject・追加の出口) */
23
+ schema: Record<string, JsonSchema>;
24
+ /** 出す値に入れるファイル(差し戻しの参考ファイルなど)を上げて、参照を受け取る */
25
+ uploadFile: (file: File) => Promise<FileRef>;
26
+ };
27
+ clipboard: {
28
+ /** クリップボードに書く(sandbox の中では使えない navigator.clipboard.writeText の代わり) */
29
+ writeText: (text: string) => Promise<void>;
30
+ };
31
+ /**
32
+ * ループ変数(この step が始まった時点の値。program の ctx.refs と同じ)。
33
+ * 予約キー `input` が run の起動入力
34
+ */
35
+ refs: TRefs;
36
+ step: {
37
+ /** 見ている人がこの step の担当か(担当でなければ out は断られる) */
38
+ isAssignee: boolean;
39
+ /**
40
+ * 値を直してよいか:担当で、全員承認のノードでないとき true
41
+ * (全員承認は全員が同じ値を承認するので、届いた値のまま決める)
42
+ */
43
+ editable: boolean;
44
+ };
45
+ };
48
46
  };
47
+ /** mawaru が承認画面に描く、human ノードの画面(React のコンポーネント) */
48
+ export type HumanView<TInput = unknown, TOutput = Record<string, unknown>, TRefs = Record<string, unknown>> = (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode;
49
49
  /**
50
- * 承認ビュー:ctx を受けて承認画面(上の帯より下の全部)を描く React コンポーネント。
51
- * 承認バーは `<ApprovalBar ctx={ctx} />` を置く(置かなければ承認バーは出ない)。
50
+ * human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
51
+ * React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
52
52
  */
53
- export type ApprovalView<TIn = unknown> = {
54
- bivariance(props: {
55
- ctx: ApprovalViewContext<TIn>;
56
- }): ReactNode;
57
- }["bivariance"];
58
- /** 承認ビューを定義する。型を付けるためだけの関数(値はそのまま返す) */
59
- export declare const defineApprovalView: <TIn = unknown>(view: ApprovalView<TIn>) => ApprovalView<TIn>;
60
- /** 手元プレビュー(npx mawaru preview human/<dir>)のケース。nodes/human/<dir>/cases.ts の export default */
61
- export type ApprovalViewCase = {
62
- name: string;
63
- /** `ctx.output`(= step の input) */
64
- input: unknown;
65
- refs?: unknown;
66
- /** 省略時は name */
67
- nodeName?: string;
68
- /** 省略時は承認待ち */
69
- phase?: ApprovalPhase;
70
- };
71
- export { ApprovalBar } from "./_approval/ApprovalBar.js";
72
- export { type ApprovalActions, useApprovalActions } from "./_approvalActions.js";
73
- export * from "./index.js";
53
+ export declare const defineHuman: <TInput = unknown, TOutput = Record<string, unknown>, TRefs = Record<string, unknown>>(render: (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode) => HumanView<TInput, TOutput, TRefs>;
54
+ export type { FileRef };
@@ -1,6 +1,5 @@
1
- /** 承認ビューを定義する。型を付けるためだけの関数(値はそのまま返す) */
2
- export const defineApprovalView = (view) => view;
3
- export { ApprovalBar } from "./_approval/ApprovalBar.js";
4
- export { useApprovalActions } from "./_approvalActions.js";
5
- // 部品キット(土台)もここから import できる
6
- export * from "./index.js";
1
+ /**
2
+ * human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
3
+ * React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
4
+ */
5
+ export const defineHuman = (render) => render;
@@ -1,5 +1,15 @@
1
+ import type { JsonSchema } from "../_schemas/node.js";
1
2
  export declare const VIEW_MESSAGE_TAG = "mawaru:view";
2
3
  export type ThemeMode = "light" | "dark";
4
+ export type HumanViewData = {
5
+ input: unknown;
6
+ inputSchema: JsonSchema;
7
+ outputSchema: Record<string, JsonSchema>;
8
+ refs: unknown;
9
+ isAssignee: boolean;
10
+ editable: boolean;
11
+ };
12
+ export type HumanRequestMethod = "out" | "fetchFileUrl" | "uploadFile" | "writeText";
3
13
  export type ViewToHost = {
4
14
  tag: typeof VIEW_MESSAGE_TAG;
5
15
  type: "ready";
@@ -11,13 +21,13 @@ export type ViewToHost = {
11
21
  tag: typeof VIEW_MESSAGE_TAG;
12
22
  type: "request";
13
23
  id: number;
14
- method: string;
24
+ method: HumanRequestMethod;
15
25
  args: unknown;
16
26
  };
17
27
  export type HostToView = {
18
28
  tag: typeof VIEW_MESSAGE_TAG;
19
- type: "ctx";
20
- ctx: unknown;
29
+ type: "data";
30
+ data: HumanViewData;
21
31
  mode: ThemeMode;
22
32
  } | {
23
33
  tag: typeof VIEW_MESSAGE_TAG;
@@ -1,5 +1,5 @@
1
- // sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の画面)とのメッセージの形。
1
+ // sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の承認画面)とのメッセージの形。
2
2
  // **公開 API ではない**:iframe の中の実行部分とホストは同じデプロイの mawaru のコード(まとめるときに
3
- // その時点の版を同梱する)なので、いつ変えてもよい(docs/tasks/wip/承認ビューの顧客repo配置.md の「互換の管理」)。
3
+ // その時点の版を同梱する)なので、いつ変えてもよい。
4
4
  // iframe に入る JS を小さく保つため、ここには型と定数だけを置く(ホストの検査は host.ts)
5
5
  export const VIEW_MESSAGE_TAG = "mawaru:view";
@@ -1,3 +1,2 @@
1
- import type { ApprovalView } from "./human.js";
2
- export declare const requestHost: <T>(method: string, args: unknown) => Promise<T>;
3
- export declare const runApprovalView: (View: ApprovalView) => void;
1
+ import type { HumanView } from "./human.js";
2
+ export declare const runHumanView: (View: HumanView) => void;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Component } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
- import { ApprovalActionsProvider, } from "./_approvalActions.js";
5
4
  import { ViewBridgeProvider } from "./_bridge.js";
5
+ import { humanPropsOf } from "./_humanProps.js";
6
6
  import { VIEW_MESSAGE_TAG, } from "./protocol.js";
7
7
  const post = (message) =>
8
8
  // 受け手は親(ホスト)の window だけ。不透明オリジンの iframe からは親のオリジンを指定できない
@@ -10,7 +10,7 @@ window.parent.postMessage({ tag: VIEW_MESSAGE_TAG, ...message }, "*");
10
10
  const messageOf = (err) => err instanceof Error ? err.message : String(err);
11
11
  let nextRequestId = 1;
12
12
  const pending = new Map();
13
- const ctxListeners = new Set();
13
+ const dataListeners = new Set();
14
14
  const onHostMessage = (event) => {
15
15
  if (event.source !== window.parent)
16
16
  return;
@@ -28,14 +28,14 @@ const onHostMessage = (event) => {
28
28
  waiting.reject(new Error(message.error));
29
29
  return;
30
30
  }
31
- if (message.type === "ctx") {
31
+ if (message.type === "data") {
32
32
  document.documentElement.dataset.mode = message.mode;
33
- for (const listener of ctxListeners)
34
- listener(message.ctx, message.mode);
33
+ for (const listener of dataListeners)
34
+ listener(message.data);
35
35
  }
36
36
  };
37
37
  // ホストに頼む(返事が来るまで待つ)
38
- export const requestHost = (method, args) => new Promise((resolve, reject) => {
38
+ const requestHost = (method, args) => new Promise((resolve, reject) => {
39
39
  const id = nextRequestId++;
40
40
  pending.set(id, {
41
41
  resolve: resolve,
@@ -43,12 +43,19 @@ export const requestHost = (method, args) => new Promise((resolve, reject) => {
43
43
  });
44
44
  post({ type: "request", id, method, args });
45
45
  });
46
- const bridge = {
47
- fileUrl: (file) => requestHost("fileUrl", { file }),
46
+ const ops = {
47
+ out: (output) => requestHost("out", { output }),
48
+ fetchFileUrl: (file) => requestHost("fetchFileUrl", { file }),
48
49
  uploadFile: (file) => requestHost("uploadFile", { file }),
49
- copyText: (text) => requestHost("copyText", { text }),
50
+ writeText: (text) => requestHost("writeText", { text }),
51
+ };
52
+ // 部品(FilePreview・SchemaForm のファイル欄)が裏で使う口も、同じ頼みごとにつなぐ
53
+ const bridge = {
54
+ fileUrl: ops.fetchFileUrl,
55
+ uploadFile: ops.uploadFile,
56
+ copyText: ops.writeText,
50
57
  };
51
- // 描画の例外をホストへ知らせる(ホストは標準ビューに差し替える)
58
+ // 描画の例外をホストへ知らせる(ホストはエラーと理由を出す)
52
59
  class ReportErrors extends Component {
53
60
  state = { failed: false };
54
61
  static getDerivedStateFromError() {
@@ -61,9 +68,9 @@ class ReportErrors extends Component {
61
68
  return this.state.failed ? null : this.props.children;
62
69
  }
63
70
  }
64
- // iframe の中の共通の準備:ホストからのメッセージ・例外の報告。
65
- // 最初の ctx が届いたら render を呼ぶ(以降の ctx も同じ render に渡す)
66
- const startView = (render) => {
71
+ // 実装はコンポーネント1つ。状態(直した値など)は実装が useState で持ち、材料が届き直しても
72
+ // 同じコンポーネントとして描き直すので保たれる
73
+ export const runHumanView = (View) => {
67
74
  window.addEventListener("message", onHostMessage);
68
75
  window.addEventListener("error", (event) => post({ type: "error", message: event.message }));
69
76
  window.addEventListener("unhandledrejection", (event) => post({ type: "error", message: messageOf(event.reason) }));
@@ -71,16 +78,9 @@ const startView = (render) => {
71
78
  if (!container)
72
79
  throw new Error("#root がありません");
73
80
  const root = createRoot(container);
74
- ctxListeners.add((ctx) => root.render(_jsx(ViewBridgeProvider, { bridge: bridge, children: _jsx(ReportErrors, { children: render(ctx) }) })));
81
+ dataListeners.add((data) => {
82
+ const props = humanPropsOf(data, ops);
83
+ root.render(_jsx(ViewBridgeProvider, { bridge: bridge, children: _jsx(ReportErrors, { children: _jsx(View, { ...props }) }) }));
84
+ });
75
85
  post({ type: "ready" });
76
86
  };
77
- // ---- 承認ビュー ----
78
- const actions = {
79
- decide: (port, value) => requestHost("decide", { port, value }),
80
- openRejectDialog: () => requestHost("openRejectDialog", {}),
81
- openExitDialog: (key, value) => requestHost("openExitDialog", { key, value }),
82
- close: () => requestHost("close", {}),
83
- };
84
- // ビューはコンポーネント1つ。状態(直した値・選択など)はビューが useState で持ち、ctx が変わっても
85
- // (局面・担当の更新)同じコンポーネントとして描き直すので保たれる
86
- export const runApprovalView = (View) => startView((ctx) => (_jsx(ApprovalActionsProvider, { value: actions, children: _jsx(View, { ctx: ctx }) })));
@@ -0,0 +1,22 @@
1
+ type Input = {
2
+ subject: string;
3
+ body: string;
4
+ };
5
+ type Outputs = {
6
+ approve: {
7
+ subject: string;
8
+ body: string;
9
+ };
10
+ reject: {
11
+ prompt: string;
12
+ };
13
+ };
14
+ type Output = {
15
+ approve: Outputs["approve"];
16
+ reject?: never;
17
+ } | {
18
+ reject: Outputs["reject"];
19
+ approve?: never;
20
+ };
21
+ declare const _default: import("./human.js").HumanView<Input, Output, Record<string, unknown>>;
22
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { defineHuman } from "./human.js";
4
+ export default defineHuman(({ input, out }) => {
5
+ const [reply, setReply] = useState(input);
6
+ const [prompt, setPrompt] = useState("");
7
+ return (_jsxs("div", { style: {
8
+ maxWidth: "720px",
9
+ margin: "0 auto",
10
+ padding: "var(--space-8) var(--space-6)",
11
+ display: "flex",
12
+ flexDirection: "column",
13
+ gap: "var(--space-3)",
14
+ }, children: [_jsx("input", { value: reply.subject, onChange: (e) => setReply({ ...reply, subject: e.target.value }), style: {
15
+ padding: "var(--space-2) var(--space-3)",
16
+ border: "var(--border-w) solid var(--border)",
17
+ borderRadius: "var(--radius-s)",
18
+ background: "var(--surface)",
19
+ color: "var(--ink)",
20
+ font: "inherit",
21
+ } }), _jsx("textarea", { value: reply.body, onChange: (e) => setReply({ ...reply, body: e.target.value }), style: {
22
+ minHeight: "16em",
23
+ padding: "var(--space-2) var(--space-3)",
24
+ border: "var(--border-w) solid var(--border)",
25
+ borderRadius: "var(--radius-s)",
26
+ background: "var(--surface)",
27
+ color: "var(--ink)",
28
+ font: "inherit",
29
+ } }), _jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: _jsx("button", { type: "button", className: "primary", disabled: reply.body.trim() === "", onClick: () => out({ approve: reply }), children: "\u3053\u306E\u5185\u5BB9\u3067\u9001\u4FE1" }) }), _jsxs("div", { style: { display: "flex", gap: "var(--space-2)" }, children: [_jsx("input", { value: prompt, placeholder: "\u5DEE\u3057\u623B\u3057\u306E\u7406\u7531", onChange: (e) => setPrompt(e.target.value), style: {
30
+ flex: 1,
31
+ padding: "var(--space-2) var(--space-3)",
32
+ border: "var(--border-w) solid var(--border)",
33
+ borderRadius: "var(--radius-s)",
34
+ background: "var(--surface)",
35
+ color: "var(--ink)",
36
+ font: "inherit",
37
+ } }), _jsx("button", { type: "button", className: "ghost", disabled: prompt.trim() === "", onClick: () => out({ reject: { prompt } }), children: "\u5DEE\u3057\u623B\u3059" })] })] }));
38
+ });
@@ -19,7 +19,7 @@ nodes/
19
19
  program/<dir>/ # Program ノード:config.json + main.ts(+任意で package.json)
20
20
  ai/<dir>/ # AI ノード定義:config.json
21
21
  hook/<dir>/ # NodeIOHook:config.json + main.ts(ノードに付く観測フック)
22
- human/<dir>/ # Human ノードの実装(承認画面):config.json + index.tsx(+手元プレビュー用の cases.ts)
22
+ human/<dir>/ # Human ノードの実装(承認画面):config.json + index.tsx
23
23
  skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
24
24
  views/
25
25
  slack/<name>/ # Slack ビュー:index.ts(+手元プレビュー用の cases.ts)
@@ -102,40 +102,39 @@ views/
102
102
 
103
103
  ## Human ノード(`nodes/human/<dir>/`)
104
104
 
105
- - human ノードの実装=承認画面の、上の帯より下の全部(書類と承認バー)を描く React のビュー。ノードの「承認画面」に
106
- ディレクトリ名を選ぶと使われる(program / AI と同じく、ブランチ(ref)も指せる)。選ばなければ mawaru の標準の承認画面。
107
- 右上の閉じるボタン・差し戻しと追加の出口のダイアログ・承認の送信は mawaru が持つ
108
- - `config.json` — 宣言(human のポート型)。形は AI の config.json に揃える:
109
- `{ "inputs": { "in": { "schema": … } }, "outputs": { "approve": …, "reject": … } }`。
110
- `inputs.in` を省略すると上流の型をそのまま受ける、`outputs.approve` を省略すると in と同型、`outputs.reject` を省略すると
111
- 標準の差し戻し(指示+参考ファイル)。`type` の配列(nullable)は使えない(任意の値は required から外す)。
112
- エディタでディレクトリを選んだときに宣言がノードへ写る。**config.json を変えたら、エディタで選び直してから publish する**
113
- (固定する commit の宣言とノードの写しが違うと publish が 422 になる。program / AI と同じ)
114
- - `index.tsx` — `export default defineApprovalView<In>(({ ctx }) => …)`。ビューはコンポーネント1つで、状態(直した値・選択など)は
115
- `useState` で持つ。**承認バーは `<ApprovalBar ctx={ctx} label=… disabled=… output=… />` を自分で置く**(置かないと
116
- 承認バーは出ない)。`output` は承認で approve に流す値(省略時は `ctx.output`)。書き方の例:
117
- `<div style={{ minHeight: "100%", display: "flex", flexDirection: "column" }}><div style={{ flex: 1 }}>…</div><ApprovalBar ctx={ctx} /></div>`
118
- (承認バーは下に貼り付く)
119
- - 契約(型・ctx の中身)と部品(`ApprovalBar`・`SchemaForm`・`Markdown`・`Linkify`・`FilePreview`・`useNarrow` など)は
120
- すべて `@mawaru/sdk/human` から import する。型は `npx mawaru typegen` が config.json から
121
- `types.d.ts`(`In` / `Approve` / `Reject`。file は `FileRef`)を作る
105
+ - human ノードの実装=「input を受け取り、人の操作で出口(approve・reject・追加の出口)に値を出す」画面。
106
+ program の `run(input, ctx)` が `{ <出口ポートのkey>: <データ> }` を返すのと同じ形で、human はそれを人が画面で選ぶ。
107
+ ノードの「承認画面」にディレクトリ名を選ぶと使われる(program / AI と同じく、ブランチ(ref)も指せる)。
108
+ 選ばなければ mawaru の標準の承認画面
109
+ - `config.json` — 入出力スキーマの宣言。正は `_schemas/human-manifest.ts`。形は AI の config.json に揃える:
110
+ `{ "inputs": { "in": { "schema": … } }, "outputs": { "approve": …, "reject": …, <追加の出口>: … } }`。
111
+ in・approve・reject は全部書く(program と同じく必須)。program / AI と同じく、エディタでディレクトリを選ぶとこの宣言から
112
+ ポートができ、エディタでポートの型を直すと保存時に config.json へ書き戻される(`refs` もエディタが転写する)
113
+ - `index.tsx` — `export default defineHuman<Input, Output, Refs>(({ input, out, ctx }) => …)`(`@mawaru/sdk/human`)。
114
+ React のコンポーネント1つで、状態(直した値・選択など)は `useState` で持つ
115
+ - `input`:届いた値(in ポートの値)
116
+ - `out({ <出口key>: 値 })`:人の操作を mawaru に渡す(出口は1つだけ)。人がボタンを押した直後の呼び出しだけを受け付ける
117
+ (開いただけで決定する画面は書けない)。送れたら承認画面は run の画面へ戻る。断られたら(担当でない・もう決まっている など)
118
+ Promise が理由つきで失敗する
119
+ - `ctx.input.schema`(in の型)・`ctx.input.fetchFileUrl(file)`(input のファイルを表示する URL)・
120
+ `ctx.output.schema`(出口ごとの型)・`ctx.output.uploadFile(file)`(出すファイルを上げて参照を受け取る。差し戻しの参考ファイルなど)・
121
+ `ctx.clipboard.writeText(text)`(コピー)・`ctx.refs`(ループ変数。`ctx.refs.input` が run の起動入力)・
122
+ `ctx.step.isAssignee`(見ている人が担当か)・`ctx.step.editable`(値を直してよいか。担当で、全員承認のノードでないとき true)
123
+ - 承認・差し戻し・追加の出口のボタンも実装が置く。差し戻しの理由や参考ファイルを入れる欄も実装が作る
124
+ - 型は `npx mawaru typegen` が config.json から `types.d.ts`(program と同じ `Input` / `Outputs` / `Output` / `Refs`。file は `FileRef`)を作る
125
+ - 共通の部品(`SchemaForm`・`Markdown`・`Linkify`・`FilePreview`・`useNarrow` など)は `@mawaru/sdk/components` から import する
126
+ (`FilePreview` と `SchemaForm` のファイル欄は、承認画面の中では自動的に mawaru に頼む口につながる)
122
127
  - エディタの型検査には repo ルートの `tsconfig.json`(`jsx: react-jsx`・`moduleResolution: Bundler`・`include: ["nodes/human", "views"]`)と
123
128
  devDependencies の `@types/react` が要る。`npx mawaru init` が作る(init より前からある repo は自分で足す)
124
- - 承認画面の上の帯より下を余白なしで全部使える。余白・最大幅・スクロールはビューが決める
125
- (縦いっぱいに使うなら `100vh`)。右上の角には mawaru の閉じるボタンが重なるので、大事なものを置かない
129
+ - 承認画面の上の帯より下を余白なしで全部使える。余白・最大幅・スクロールは実装が決める
130
+ (縦いっぱいに使うなら `100vh`)。右上の角には mawaru の閉じるボタンが重なるので、大事なものを置かない。
131
+ 局面・担当・全員承認の進み具合は mawaru の帯が出し、実装が描かれるのは承認待ちのときだけ
126
132
  - mawaru がまとめて(ビルドして)**sandbox iframe の中で動かす**:mawaru のログインや API には届かない。`fetch` も CSP で止まる。
127
- 要るものは部品キットに頼む:`useViewBridge()` の `fileUrl`(署名付き URL)・`uploadFile`・`copyText`、
128
- `useApprovalActions()` の `decide(port, value)`(承認・差し戻し・追加の出口を決める。決定できるとき
129
- (`ctx.decision.canDecide`)、人が押した直後だけ通る)・`openRejectDialog()`・`openExitDialog(key, value)`・`close()`。
130
- `ApprovalBar` はこれらを使っているので、置くだけなら呼ばなくてよい
131
- - import できるのは `nodes/human/` の中の相対パス・`react`・`@mawaru/sdk/human` だけ
132
- (npm パッケージは不可)。複数の human ノードで使う部品は `nodes/human/_<名前>/` に置ける(`_` 始まりはノードではない)
133
- - 例外を投げる・まとめられないと、その承認画面は標準に落ちる。URL に `?view=standard` を付けると標準で開ける
133
+ 要るものは `ctx` の口に頼む
134
+ - import できるのは同じディレクトリの中の相対パス・`react`・`@mawaru/sdk/human`・`@mawaru/sdk/components` だけ(npm パッケージは不可)
135
+ - 例外を投げる・まとめられないと、承認画面にエラーと理由が出る
134
136
  - 版:program / AI と同じ。公開版は publish 時の commit に固定され、テスト実行は push だけで変わる
135
- - `cases.ts` — 手元プレビューのケース(`ApprovalViewCase[]` を `export default`。`{ name, input, refs?, nodeName?, phase? }`)
136
- - 手元プレビュー:`npx mawaru login` のあと、repo ルートで `npx mawaru preview human/<dir>`。push せずに手元のファイルを
137
- 本番と同じまとめ方・同じ隔離で、承認画面と同じ形で開く(ケースと局面を切り替えられ、承認は送らずに approve に載る値を出す)。
138
- 保存するたびに送り直し、開いている画面が読み直す。まとめられないと理由を出して exit 1。`--once` は1回送って終わる
137
+ - 試し方:ブランチに push し、ノードの ref にそのブランチを入れてテスト実行する
139
138
 
140
139
  ## loop(回路)の作成・更新
141
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mawaru/sdk",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/views/human.d.ts",
16
16
  "default": "./dist/views/human.js"
17
17
  },
18
+ "./components": {
19
+ "types": "./dist/views/components.d.ts",
20
+ "default": "./dist/views/components.js"
21
+ },
18
22
  "./views/slack": {
19
23
  "types": "./dist/views/slack.d.ts",
20
24
  "default": "./dist/views/slack.js"
@@ -49,7 +49,7 @@ secret)でも、**足りない値を推測・生成・空文字で代用しな
49
49
  ノード・接続・refs(ループ変数)・hooks の形と、superRefine 内の全整合性ルール(日本語コメント付き)
50
50
  - `node_modules/@mawaru/sdk/_schemas/node.ts` — ポートの形(`graphPortSchema`)と key の規則
51
51
  - `node_modules/@mawaru/sdk/_schemas/port-spec.ts` — **kind 別の必須ポート構成**(`kindPortsViolation`)と
52
- 接続の型規則(human の in は実装(nodes/human/)の宣言型、program の in はスキーマ包含 等)
52
+ 接続の型規則(実装(nodes/human/)を指す human は program と同じ宣言型、program の in はスキーマ包含 等)
53
53
  - `node_modules/@mawaru/sdk/_schemas/program-manifest.ts` / `ai-manifest.ts` / `human-manifest.ts` — repo 側 config.json の規約
54
54
  - 同ディレクトリの `*.test.ts` — 通る graph / 弾かれる graph の実例集として読める
55
55
 
@@ -125,7 +125,8 @@ graph の保存(`PUT loops/{loopId}/graph`)は常に下書き(draft)に
125
125
  out ポートは outputs の各 key)。human は承認画面、wait は待ち方もここで設定する。
126
126
  human は標準の承認画面なら `human.human_dir` を省略する。この repo に専用の承認画面が要るなら
127
127
  `nodes/human/<dir>/` に作り(`docs/development.md` の「Human ノード」)、push してから `human.human_dir`(+任意で `ref`)に
128
- ディレクトリ名を入れる(`GET github/humans` が候補を返す)。`human.declaration` は省略してよい(保存時に mawaru が repo から読む)。
128
+ ディレクトリ名を入れる(`GET github/humans` が候補を返す)。ポートは program と同じく `GET github/humans/:dir/config` の
129
+ manifest(inputs / outputs)から起こす。
129
130
  3. **単体で確かめる**:そのノードだけをテスト実行する。`POST runs/start` に `kind: "test"`・
130
131
  `test_scope: "only"`・`start_node_id`(そのノード)・`payload`(サンプル入力。上流を確かめ済みなら
131
132
  その output を使う)・`start_by`(ユーザーの user_id。`GET members` で引く。API キー起動では必須)を
@@ -9,6 +9,8 @@
9
9
  - `nodes/program/<dir>/` — Program ノード(`config.json` + `main.ts`)。`main.ts` は
10
10
  `run(input, ctx)` を export し、戻り値は `{ <出口ポートのkey>: <データ> }` の単一キーのオブジェクト
11
11
  - `nodes/ai/<dir>/` — AI ノード定義(`config.json`。prompt / model / skills / 入出力スキーマ / env)
12
+ - `nodes/human/<dir>/` — Human ノードの実装(承認画面。`config.json` + `index.tsx`)。`index.tsx` は
13
+ `export default defineHuman(({ input, out, ctx }) => …)` で、人の操作を `out({ <出口ポートのkey>: <データ> })` で渡す
12
14
  - `views/slack/<name>/` — Slack ビュー(実行中・承認待ち・完了メッセージの本文)。`index.ts` + 手元プレビュー用の `cases.ts`
13
15
  - `.github/workflows/mawaru-runner.yml` — mawaru 所有の実行シム。**編集しない**
14
16
  (`npx @mawaru/sdk init` が再生成する)
@@ -0,0 +1,27 @@
1
+ import type { FileRef } from "./_parts/fileRef.js"
2
+ import type { HumanProps } from "./human.js"
3
+ import type { HumanViewData } from "./protocol.js"
4
+
5
+ // human ノードの実装に渡す props を組み立てる(iframe の中の実行部分と、mawaru の中の標準の承認画面で同じものを使う)。
6
+ // 関数の中身は呼ぶ側が差す:iframe の中はホストへの頼みごと、ホストの画面は API を直接呼ぶ
7
+ export type HumanOps = {
8
+ out: (output: Record<string, unknown>) => Promise<void>
9
+ fetchFileUrl: (file: FileRef) => Promise<string>
10
+ uploadFile: (file: File) => Promise<FileRef>
11
+ writeText: (text: string) => Promise<void>
12
+ }
13
+
14
+ export const humanPropsOf = (
15
+ data: HumanViewData,
16
+ ops: HumanOps,
17
+ ): HumanProps => ({
18
+ input: data.input,
19
+ out: ops.out,
20
+ ctx: {
21
+ input: { schema: data.inputSchema, fetchFileUrl: ops.fetchFileUrl },
22
+ output: { schema: data.outputSchema, uploadFile: ops.uploadFile },
23
+ clipboard: { writeText: ops.writeText },
24
+ refs: (data.refs ?? {}) as Record<string, unknown>,
25
+ step: { isAssignee: data.isAssignee, editable: data.editable },
26
+ },
27
+ })
@@ -0,0 +1,16 @@
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 { type FileRef, 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 {
12
+ asFormSchema,
13
+ type JsonSchema as FormSchema,
14
+ SchemaForm,
15
+ } from "./_parts/SchemaForm.js"
16
+ export { NARROW_WIDTH, useNarrow } from "./_parts/useNarrow.js"
package/views/host.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import { z } from "zod"
2
2
  import { VIEW_MESSAGE_TAG } from "./protocol.js"
3
3
 
4
- export { ApprovalActionsProvider } from "./_approvalActions.js"
5
4
  // ホスト(mawaru の画面)だけが使うもの(公開 API ではない):sandbox iframe からのメッセージを確かめる形
6
- // (protocol.ts の型と同じ。iframe の中は信用できないコードなので、形を確かめてから使う)と、
7
- // ビューに頼む口を差す提供部品
8
- export { ViewBridgeProvider } from "./_bridge.js"
5
+ // (protocol.ts の型と同じ。iframe の中は信用できないコードなので、形を確かめてから使う)、
6
+ // 部品が裏で使う口を差す提供部品、human ノードの実装に渡す props の組み立て(標準の承認画面も同じものを使う)
7
+ export {
8
+ useViewBridge,
9
+ type ViewBridge,
10
+ ViewBridgeProvider,
11
+ } from "./_bridge.js"
12
+ export { type HumanOps, humanPropsOf } from "./_humanProps.js"
9
13
  export * from "./protocol.js"
10
14
 
11
15
  const tag = z.literal(VIEW_MESSAGE_TAG)
@@ -17,7 +21,7 @@ export const viewToHostSchema = z.discriminatedUnion("type", [
17
21
  tag,
18
22
  type: z.literal("request"),
19
23
  id: z.number().int(),
20
- method: z.string(),
24
+ method: z.enum(["out", "fetchFileUrl", "uploadFile", "writeText"]),
21
25
  args: z.unknown(),
22
26
  }),
23
27
  ])