@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.
- package/_schemas/graph.ts +0 -5
- package/_schemas/human-manifest.test.ts +39 -101
- package/_schemas/human-manifest.ts +25 -152
- package/_schemas/port-spec.test.ts +33 -92
- package/_schemas/port-spec.ts +35 -44
- package/dist/_cli/login.js +15 -1
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +0 -8
- package/dist/_cli/previewCommand.js +37 -95
- package/dist/_cli/previewFiles.d.ts +1 -2
- package/dist/_cli/previewFiles.js +18 -42
- package/dist/_schemas/graph.d.ts +0 -15
- package/dist/_schemas/graph.js +0 -5
- package/dist/_schemas/human-manifest.d.ts +6 -24
- package/dist/_schemas/human-manifest.js +24 -116
- package/dist/_schemas/port-spec.d.ts +1 -4
- package/dist/_schemas/port-spec.js +25 -34
- package/dist/cli.js +1 -2
- package/dist/typegen.d.ts +2 -2
- package/dist/typegen.js +21 -21
- package/dist/validate.js +9 -7
- package/dist/views/_humanProps.d.ts +10 -0
- package/dist/views/_humanProps.js +11 -0
- package/dist/views/{index.d.ts → components.d.ts} +0 -1
- package/dist/views/components.js +12 -0
- package/dist/views/host.d.ts +8 -3
- package/dist/views/host.js +5 -5
- package/dist/views/human.d.ts +49 -68
- package/dist/views/human.js +5 -6
- package/dist/views/protocol.d.ts +13 -3
- package/dist/views/protocol.js +2 -2
- package/dist/views/runtime.d.ts +2 -3
- package/dist/views/runtime.js +24 -24
- package/dist/views/sample.d.ts +22 -0
- package/dist/views/sample.js +38 -0
- package/docs/development.md +30 -31
- package/package.json +5 -1
- package/skills/create-loop/SKILL.md +3 -2
- package/templates/CLAUDE.md +2 -0
- package/views/_humanProps.ts +27 -0
- package/views/components.ts +16 -0
- package/views/host.ts +9 -5
- package/views/human.ts +71 -79
- package/views/protocol.ts +29 -6
- package/views/runtime.tsx +36 -45
- package/views/sample.tsx +94 -0
- package/dist/_cli/openBrowser.d.ts +0 -1
- package/dist/_cli/openBrowser.js +0 -15
- package/dist/views/_approval/ApprovalBar.d.ts +0 -20
- package/dist/views/_approval/ApprovalBar.js +0 -74
- package/dist/views/_approvalActions.d.ts +0 -18
- package/dist/views/_approvalActions.js +0 -10
- package/dist/views/index.js +0 -12
- package/views/_approval/ApprovalBar.tsx +0 -239
- package/views/_approvalActions.tsx +0 -31
- package/views/index.ts +0 -16
package/dist/views/human.d.ts
CHANGED
|
@@ -1,73 +1,54 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { JsonSchema } from "../_schemas/node.js";
|
|
3
|
-
|
|
4
|
-
export type
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
|
|
51
|
+
* React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
|
|
52
52
|
*/
|
|
53
|
-
export
|
|
54
|
-
|
|
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 };
|
package/dist/views/human.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export * from "./index.js";
|
|
1
|
+
/**
|
|
2
|
+
* human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
|
|
3
|
+
* React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
|
|
4
|
+
*/
|
|
5
|
+
export const defineHuman = (render) => render;
|
package/dist/views/protocol.d.ts
CHANGED
|
@@ -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:
|
|
24
|
+
method: HumanRequestMethod;
|
|
15
25
|
args: unknown;
|
|
16
26
|
};
|
|
17
27
|
export type HostToView = {
|
|
18
28
|
tag: typeof VIEW_MESSAGE_TAG;
|
|
19
|
-
type: "
|
|
20
|
-
|
|
29
|
+
type: "data";
|
|
30
|
+
data: HumanViewData;
|
|
21
31
|
mode: ThemeMode;
|
|
22
32
|
} | {
|
|
23
33
|
tag: typeof VIEW_MESSAGE_TAG;
|
package/dist/views/protocol.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru
|
|
1
|
+
// sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の承認画面)とのメッセージの形。
|
|
2
2
|
// **公開 API ではない**:iframe の中の実行部分とホストは同じデプロイの mawaru のコード(まとめるときに
|
|
3
|
-
//
|
|
3
|
+
// その時点の版を同梱する)なので、いつ変えてもよい。
|
|
4
4
|
// iframe に入る JS を小さく保つため、ここには型と定数だけを置く(ホストの検査は host.ts)
|
|
5
5
|
export const VIEW_MESSAGE_TAG = "mawaru:view";
|
package/dist/views/runtime.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const runApprovalView: (View: ApprovalView) => void;
|
|
1
|
+
import type { HumanView } from "./human.js";
|
|
2
|
+
export declare const runHumanView: (View: HumanView) => void;
|
package/dist/views/runtime.js
CHANGED
|
@@ -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
|
|
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 === "
|
|
31
|
+
if (message.type === "data") {
|
|
32
32
|
document.documentElement.dataset.mode = message.mode;
|
|
33
|
-
for (const listener of
|
|
34
|
-
listener(message.
|
|
33
|
+
for (const listener of dataListeners)
|
|
34
|
+
listener(message.data);
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
// ホストに頼む(返事が来るまで待つ)
|
|
38
|
-
|
|
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
|
|
47
|
-
|
|
46
|
+
const ops = {
|
|
47
|
+
out: (output) => requestHost("out", { output }),
|
|
48
|
+
fetchFileUrl: (file) => requestHost("fetchFileUrl", { file }),
|
|
48
49
|
uploadFile: (file) => requestHost("uploadFile", { file }),
|
|
49
|
-
|
|
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
|
-
//
|
|
65
|
-
//
|
|
66
|
-
const
|
|
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
|
-
|
|
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
|
+
});
|
package/docs/development.md
CHANGED
|
@@ -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
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`inputs
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
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`
|
|
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 キー起動では必須)を
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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 {
|
|
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.
|
|
24
|
+
method: z.enum(["out", "fetchFileUrl", "uploadFile", "writeText"]),
|
|
21
25
|
args: z.unknown(),
|
|
22
26
|
}),
|
|
23
27
|
])
|