@mawaru/sdk 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_schemas/ai-manifest.test.ts +8 -0
- package/_schemas/ai-manifest.ts +2 -0
- package/_schemas/graph.test.ts +51 -9
- package/_schemas/graph.ts +23 -8
- package/_schemas/human-manifest.test.ts +129 -0
- package/_schemas/human-manifest.ts +158 -0
- package/_schemas/node.ts +4 -0
- package/_schemas/port-spec.test.ts +164 -40
- package/_schemas/port-spec.ts +77 -26
- package/dist/_cli/login.js +1 -15
- package/dist/_cli/openBrowser.d.ts +1 -0
- package/dist/_cli/openBrowser.js +15 -0
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +8 -0
- package/dist/_cli/previewCommand.js +95 -37
- package/dist/_cli/previewFiles.d.ts +2 -1
- package/dist/_cli/previewFiles.js +42 -19
- package/dist/_cli/previewSlack.d.ts +1 -1
- package/dist/_cli/previewSlack.js +1 -1
- package/dist/_schemas/ai-manifest.d.ts +3 -0
- package/dist/_schemas/ai-manifest.js +2 -0
- package/dist/_schemas/graph.d.ts +23 -3
- package/dist/_schemas/graph.js +20 -8
- package/dist/_schemas/human-manifest.d.ts +27 -0
- package/dist/_schemas/human-manifest.js +121 -0
- package/dist/_schemas/node.d.ts +1 -0
- package/dist/_schemas/node.js +4 -0
- package/dist/_schemas/port-spec.d.ts +8 -9
- package/dist/_schemas/port-spec.js +64 -9
- package/dist/cli.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +22 -3
- package/dist/typegen.d.ts +3 -1
- package/dist/typegen.js +96 -14
- package/dist/validate.js +55 -0
- package/dist/views/_approval/ApprovalBar.d.ts +20 -0
- package/dist/views/_approval/ApprovalBar.js +74 -0
- package/dist/views/_approvalActions.d.ts +18 -0
- package/dist/views/_approvalActions.js +10 -0
- package/dist/views/_bridge.d.ts +12 -0
- package/dist/views/_bridge.js +10 -0
- package/dist/views/_parts/DocumentCard.d.ts +2 -0
- package/dist/views/_parts/DocumentCard.js +16 -0
- package/dist/views/_parts/FilePreview.d.ts +5 -0
- package/dist/views/_parts/FilePreview.js +101 -0
- package/dist/views/_parts/Linkify.d.ts +7 -0
- package/dist/views/_parts/Linkify.js +28 -0
- package/dist/views/_parts/Markdown.d.ts +44 -0
- package/dist/views/_parts/Markdown.js +313 -0
- package/dist/views/_parts/SchemaForm.d.ts +19 -0
- package/dist/views/_parts/SchemaForm.js +310 -0
- package/dist/views/_parts/fileRef.d.ts +7 -0
- package/dist/views/_parts/fileRef.js +11 -0
- package/dist/views/_parts/useNarrow.d.ts +5 -0
- package/dist/views/_parts/useNarrow.js +23 -0
- package/dist/views/host.d.ts +18 -0
- package/dist/views/host.js +20 -0
- package/dist/views/human.d.ts +73 -0
- package/dist/views/human.js +6 -0
- package/dist/views/index.d.ts +8 -0
- package/dist/views/index.js +12 -0
- package/dist/views/protocol.d.ts +34 -0
- package/dist/views/protocol.js +5 -0
- package/dist/views/runtime.d.ts +3 -0
- package/dist/views/runtime.js +86 -0
- package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
- package/docs/development.md +45 -5
- package/index.ts +1 -0
- package/package.json +36 -4
- package/skills/create-loop/SKILL.md +6 -3
- package/views/_approval/ApprovalBar.tsx +239 -0
- package/views/_approvalActions.tsx +31 -0
- package/views/_bridge.tsx +36 -0
- package/views/_parts/DocumentCard.ts +18 -0
- package/views/_parts/FilePreview.tsx +180 -0
- package/views/_parts/Linkify.test.ts +40 -0
- package/views/_parts/Linkify.tsx +40 -0
- package/views/_parts/Markdown.test.ts +131 -0
- package/views/_parts/Markdown.tsx +472 -0
- package/views/_parts/SchemaForm.tsx +742 -0
- package/views/_parts/fileRef.ts +21 -0
- package/views/_parts/useNarrow.ts +24 -0
- package/views/host.ts +23 -0
- package/views/human.ts +91 -0
- package/views/index.ts +16 -0
- package/views/protocol.ts +40 -0
- package/views/runtime.tsx +132 -0
- package/views/slack.test.ts +40 -0
- package/{slack-view.ts → views/slack.ts} +3 -3
- /package/dist/{slack-view.js → views/slack.js} +0 -0
package/docs/development.md
CHANGED
|
@@ -19,6 +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
23
|
skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
|
|
23
24
|
views/
|
|
24
25
|
slack/<name>/ # Slack ビュー:index.ts(+手元プレビュー用の cases.ts)
|
|
@@ -56,10 +57,12 @@ views/
|
|
|
56
57
|
走り続ける)。省略時は `claude-opus`。認証は repo の Actions secrets に置く:Claude Code は
|
|
57
58
|
`ANTHROPIC_API_KEY`(または `CLAUDE_CODE_OAUTH_TOKEN`)、Codex は明示的な `auth_mode` と対応する認証情報(下記「Codex の認証方式」参照)
|
|
58
59
|
(`env` に宣言しなくても渡される)
|
|
59
|
-
- `inputs` は1件以上・key 自由。値は `{ "schema": <JSON Schema>, "resume": true }` の形
|
|
60
|
+
- `inputs` は1件以上・key 自由。値は `{ "schema": <JSON Schema>, "resume": true, "flexible": true }` の形
|
|
60
61
|
(`resume` は省略可。true の口から入力が届いた実行は、同じノードの直近セッションを
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
復元して再開する——差し戻し→修正の続き実行に使う。`flexible` も省略可。true の口は
|
|
63
|
+
`schema` を推奨型として維持し、接続で型が上書きされず型の違う接続も通る——この AI が
|
|
64
|
+
先頭のループを他のループや API から型を揃えずに呼べるようにする)。旧形式(値が素の
|
|
65
|
+
JSON Schema)も読める
|
|
63
66
|
- 実行時はラッパーがエージェント CLI(Claude Code / Codex、非対話)を起動し、`outputs` の
|
|
64
67
|
スキーマに適合する output を検証して報告する。出口が複数あれば AI が1つ選ぶ(複数出口=分岐)
|
|
65
68
|
- **文体の既定は「短く」**:出力の文章は承認画面・Slack・顧客への回答としてそのまま人が読むため、
|
|
@@ -79,10 +82,10 @@ views/
|
|
|
79
82
|
ディレクトリ名で出る)。タイトル行・追加出口のボタン・投稿は mawaru が描く
|
|
80
83
|
- `index.ts` — `export default { files?, renderRunning?, renderCompleted? } satisfies SlackView<入力の型>`。
|
|
81
84
|
契約(型・ctx の中身・決定ボタンの `decisionActionId` / `decisionFormActionId`)の正は
|
|
82
|
-
`@mawaru/sdk/slack
|
|
85
|
+
`@mawaru/sdk/views/slack`。実装した関数の面だけが変わる
|
|
83
86
|
- **純関数**にする:mawaru が隔離環境(QuickJS)で動かすので、`fetch` / `process` / `require` / タイマー /
|
|
84
87
|
`console` は無い。1回の呼び出しは 200ms・32MB まで。ctx も戻り値も JSON で受け渡す
|
|
85
|
-
- import できるのは同じディレクトリ内の相対パスと `@mawaru/sdk/slack
|
|
88
|
+
- import できるのは同じディレクトリ内の相対パスと `@mawaru/sdk/views/slack` だけ(npm パッケージは不可)。
|
|
86
89
|
ビルドしない(mawaru が TypeScript の型を外して読む。`enum` など型を外すだけで JS にならない書き方は不可)
|
|
87
90
|
- 入力は崩れて届くことがある(`input` が `null` 等)。例外を投げない書き方にする。失敗すると、承認待ちは
|
|
88
91
|
承認ボタンだけ・完了は1行のフォールバックになる
|
|
@@ -97,6 +100,43 @@ views/
|
|
|
97
100
|
- 端末に1通ごとの結果が出る(`✔` 成功・`⚠` 描画に失敗して理由つき・`–` 未実装の面)。失敗が1つでもあれば exit 1。
|
|
98
101
|
ビューを直したら同じコマンドで送り直して確かめる
|
|
99
102
|
|
|
103
|
+
## Human ノード(`nodes/human/<dir>/`)
|
|
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`)を作る
|
|
122
|
+
- エディタの型検査には repo ルートの `tsconfig.json`(`jsx: react-jsx`・`moduleResolution: Bundler`・`include: ["nodes/human", "views"]`)と
|
|
123
|
+
devDependencies の `@types/react` が要る。`npx mawaru init` が作る(init より前からある repo は自分で足す)
|
|
124
|
+
- 承認画面の上の帯より下を余白なしで全部使える。余白・最大幅・スクロールはビューが決める
|
|
125
|
+
(縦いっぱいに使うなら `100vh`)。右上の角には mawaru の閉じるボタンが重なるので、大事なものを置かない
|
|
126
|
+
- 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` を付けると標準で開ける
|
|
134
|
+
- 版: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回送って終わる
|
|
139
|
+
|
|
100
140
|
## loop(回路)の作成・更新
|
|
101
141
|
|
|
102
142
|
- `.claude/skills/create-loop` スキルに従う(graph API の契約は `_schemas/graph.ts` が正)
|
package/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./_schemas/duplicate-selection.js"
|
|
|
6
6
|
export * from "./_schemas/extract-component.js"
|
|
7
7
|
export * from "./_schemas/graph.js"
|
|
8
8
|
export * from "./_schemas/hook-manifest.js"
|
|
9
|
+
export * from "./_schemas/human-manifest.js"
|
|
9
10
|
export * from "./_schemas/node.js"
|
|
10
11
|
export * from "./_schemas/port-spec.js"
|
|
11
12
|
export * from "./_schemas/program-manifest.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mawaru/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,9 +11,25 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./human": {
|
|
15
|
+
"types": "./dist/views/human.d.ts",
|
|
16
|
+
"default": "./dist/views/human.js"
|
|
17
|
+
},
|
|
18
|
+
"./views/slack": {
|
|
19
|
+
"types": "./dist/views/slack.d.ts",
|
|
20
|
+
"default": "./dist/views/slack.js"
|
|
21
|
+
},
|
|
14
22
|
"./slack-view": {
|
|
15
|
-
"types": "./dist/slack
|
|
16
|
-
"default": "./dist/slack
|
|
23
|
+
"types": "./dist/views/slack.d.ts",
|
|
24
|
+
"default": "./dist/views/slack.js"
|
|
25
|
+
},
|
|
26
|
+
"./views/runtime": {
|
|
27
|
+
"types": "./dist/views/runtime.d.ts",
|
|
28
|
+
"default": "./dist/views/runtime.js"
|
|
29
|
+
},
|
|
30
|
+
"./views/host": {
|
|
31
|
+
"types": "./dist/views/host.d.ts",
|
|
32
|
+
"default": "./dist/views/host.js"
|
|
17
33
|
}
|
|
18
34
|
},
|
|
19
35
|
"files": [
|
|
@@ -23,7 +39,7 @@
|
|
|
23
39
|
"docs",
|
|
24
40
|
"_schemas",
|
|
25
41
|
"index.ts",
|
|
26
|
-
"
|
|
42
|
+
"views"
|
|
27
43
|
],
|
|
28
44
|
"engines": {
|
|
29
45
|
"node": ">=20"
|
|
@@ -32,8 +48,24 @@
|
|
|
32
48
|
"zod": "^4.4.3",
|
|
33
49
|
"zod-from-json-schema": "^0.5.3"
|
|
34
50
|
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "^19.2.7",
|
|
53
|
+
"react-dom": "^19.2.7"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react-dom": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
35
63
|
"devDependencies": {
|
|
36
64
|
"@types/node": "^26.1.0",
|
|
65
|
+
"@types/react": "^19.2.17",
|
|
66
|
+
"@types/react-dom": "^19.2.3",
|
|
67
|
+
"react": "^19.2.7",
|
|
68
|
+
"react-dom": "^19.2.7",
|
|
37
69
|
"typescript": "^6.0.3",
|
|
38
70
|
"vitest": "^4.1.9"
|
|
39
71
|
},
|
|
@@ -49,8 +49,8 @@ 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
|
|
53
|
-
- `node_modules/@mawaru/sdk/_schemas/program-manifest.ts` / `ai-manifest.ts` — repo 側 config.json の規約
|
|
52
|
+
接続の型規則(human の in は実装(nodes/human/)の宣言型、program の in はスキーマ包含 等)
|
|
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
|
|
|
56
56
|
## API(パスとボディは openapi.json が正)
|
|
@@ -122,7 +122,10 @@ graph の保存(`PUT loops/{loopId}/graph`)は常に下書き(draft)に
|
|
|
122
122
|
graph 保存の書き戻しと食い違って 409 になり、テスト実行も古い中身で走る)。
|
|
123
123
|
2. **ノードに結びつける**:graph の該当ノードに `program.program_dir` / `ai.ai_dir` を設定し、
|
|
124
124
|
ports を config.json の inputs / outputs の写しにして保存する(in ポート key は `in`、
|
|
125
|
-
out ポートは outputs の各 key)。human
|
|
125
|
+
out ポートは outputs の各 key)。human は承認画面、wait は待ち方もここで設定する。
|
|
126
|
+
human は標準の承認画面なら `human.human_dir` を省略する。この repo に専用の承認画面が要るなら
|
|
127
|
+
`nodes/human/<dir>/` に作り(`docs/development.md` の「Human ノード」)、push してから `human.human_dir`(+任意で `ref`)に
|
|
128
|
+
ディレクトリ名を入れる(`GET github/humans` が候補を返す)。`human.declaration` は省略してよい(保存時に mawaru が repo から読む)。
|
|
126
129
|
3. **単体で確かめる**:そのノードだけをテスト実行する。`POST runs/start` に `kind: "test"`・
|
|
127
130
|
`test_scope: "only"`・`start_node_id`(そのノード)・`payload`(サンプル入力。上流を確かめ済みなら
|
|
128
131
|
その output を使う)・`start_by`(ユーザーの user_id。`GET members` で引く。API キー起動では必須)を
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { type ReactNode, useState } from "react"
|
|
2
|
+
import { useApprovalActions } from "../_approvalActions.js"
|
|
3
|
+
import type { ApprovalViewContext } from "../human.js"
|
|
4
|
+
|
|
5
|
+
// 承認バー(承認画面の下の帯)。ビューが自分で置く部品で、内蔵の標準ビューも同じものを使う
|
|
6
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
|
|
7
|
+
// 材料は ctx.decision、操作は useApprovalActions()。送信中とエラーは decide の結果からここで持つ
|
|
8
|
+
|
|
9
|
+
const errorMessage = (err: unknown) =>
|
|
10
|
+
err instanceof Error ? err.message : "送信に失敗しました"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
|
|
14
|
+
* 中身を `flex: 1` にすると、中身が短くても下端に来る。
|
|
15
|
+
*/
|
|
16
|
+
export const ApprovalBar = <TOut = unknown>({
|
|
17
|
+
ctx,
|
|
18
|
+
label,
|
|
19
|
+
disabled = false,
|
|
20
|
+
output,
|
|
21
|
+
value,
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
/** ビューが受け取った ctx */
|
|
25
|
+
ctx: ApprovalViewContext<unknown>
|
|
26
|
+
/** 承認ボタンの文言(省略時は「承認して〈次のノード〉へ」) */
|
|
27
|
+
label?: string
|
|
28
|
+
/** 承認ボタンを押せない */
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
/** 承認したとき approve に流す値(宣言の outputs.approve の形。省略時は value)。`<ApprovalBar<Approve> …>` で型を検査できる */
|
|
31
|
+
output?: TOut
|
|
32
|
+
/** 今の値(ビューで直した値。in の形。省略時は ctx.output)。型の合う追加の出口にはこれが流れる */
|
|
33
|
+
value?: unknown
|
|
34
|
+
/** バーの左側に足すもの */
|
|
35
|
+
children?: ReactNode
|
|
36
|
+
}) => {
|
|
37
|
+
const actions = useApprovalActions()
|
|
38
|
+
const [sending, setSending] = useState(false)
|
|
39
|
+
const [error, setError] = useState<string | null>(null)
|
|
40
|
+
const { phase, decision } = ctx
|
|
41
|
+
const current = value === undefined ? ctx.output : value
|
|
42
|
+
const approveValue = output === undefined ? current : output
|
|
43
|
+
|
|
44
|
+
const send = (request: Promise<void>) => {
|
|
45
|
+
setSending(true)
|
|
46
|
+
setError(null)
|
|
47
|
+
request
|
|
48
|
+
.catch((err: unknown) => setError(errorMessage(err)))
|
|
49
|
+
.finally(() => setSending(false))
|
|
50
|
+
}
|
|
51
|
+
const open = (request: Promise<void>) =>
|
|
52
|
+
request.catch((err: unknown) => setError(errorMessage(err)))
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
style={{
|
|
57
|
+
position: "sticky",
|
|
58
|
+
bottom: 0,
|
|
59
|
+
zIndex: 1,
|
|
60
|
+
flex: "none",
|
|
61
|
+
display: "flex",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
gap: "var(--space-3)",
|
|
64
|
+
padding: "var(--space-3) var(--space-6)",
|
|
65
|
+
borderTop:
|
|
66
|
+
phase === "waiting"
|
|
67
|
+
? "var(--border-w-strong) solid var(--human)"
|
|
68
|
+
: "var(--border-w) solid var(--border)",
|
|
69
|
+
background: "var(--surface)",
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{phase === "waiting" ? (
|
|
73
|
+
<>
|
|
74
|
+
{/* 承認できない理由だけ出す(渡し先はボタンのラベルが言っている) */}
|
|
75
|
+
{decision.otherAssignees && (
|
|
76
|
+
<span style={{ fontSize: "var(--text-sm)", color: "var(--muted)" }}>
|
|
77
|
+
<span style={{ color: "var(--ink)" }}>
|
|
78
|
+
{decision.otherAssignees.join("・")}
|
|
79
|
+
</span>
|
|
80
|
+
{" さんの担当です — あなたは承認・差し戻しができません"}
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
{children}
|
|
84
|
+
<div style={{ flex: 1 }} />
|
|
85
|
+
{decision.progress && (
|
|
86
|
+
// 全員の承認が要るときの進み具合。自分が承認済みなら残りを待つ
|
|
87
|
+
<span
|
|
88
|
+
style={{
|
|
89
|
+
fontFamily: "var(--font-mono)",
|
|
90
|
+
fontSize: "var(--text-xs)",
|
|
91
|
+
color: "var(--muted)",
|
|
92
|
+
whiteSpace: "nowrap",
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{decision.progress.done}/{decision.progress.total}人 承認済み
|
|
96
|
+
</span>
|
|
97
|
+
)}
|
|
98
|
+
{error && (
|
|
99
|
+
<span role="alert" style={{ color: "var(--fail)" }}>
|
|
100
|
+
{error}
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
{decision.exits.map((exit) => (
|
|
104
|
+
<button
|
|
105
|
+
key={exit.key}
|
|
106
|
+
type="button"
|
|
107
|
+
className="ghost"
|
|
108
|
+
disabled={!decision.canDecide || sending}
|
|
109
|
+
onClick={() =>
|
|
110
|
+
exit.needsInput
|
|
111
|
+
? open(actions.openExitDialog(exit.key, current))
|
|
112
|
+
: send(actions.decide(exit.key, current))
|
|
113
|
+
}
|
|
114
|
+
style={{ fontSize: "var(--text-sm)", whiteSpace: "nowrap" }}
|
|
115
|
+
>
|
|
116
|
+
{exit.name}
|
|
117
|
+
</button>
|
|
118
|
+
))}
|
|
119
|
+
{decision.rejectTargetName && (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className="ghost"
|
|
123
|
+
disabled={!decision.canDecide || sending}
|
|
124
|
+
onClick={() => open(actions.openRejectDialog())}
|
|
125
|
+
style={{ fontSize: "var(--text-sm)", whiteSpace: "nowrap" }}
|
|
126
|
+
>
|
|
127
|
+
AIへ差し戻す(reject)
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
className="primary"
|
|
133
|
+
disabled={!decision.canDecide || sending || disabled}
|
|
134
|
+
onClick={() => send(actions.decide("approve", approveValue))}
|
|
135
|
+
style={{
|
|
136
|
+
display: "flex",
|
|
137
|
+
alignItems: "center",
|
|
138
|
+
gap: "var(--space-2)",
|
|
139
|
+
paddingLeft: "var(--space-5)",
|
|
140
|
+
paddingRight: "var(--space-5)",
|
|
141
|
+
whiteSpace: "nowrap",
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{sending
|
|
145
|
+
? "承認しています…"
|
|
146
|
+
: decision.progress?.approvedByMe
|
|
147
|
+
? "承認済み — 全員の承認待ち"
|
|
148
|
+
: (label ?? `承認して${decision.approveTargetName ?? "次"}へ`)}
|
|
149
|
+
<svg width="12" height="12" viewBox="0 0 16 16" aria-hidden="true">
|
|
150
|
+
<path
|
|
151
|
+
d="M4.5 3L12.5 8L4.5 13V3Z"
|
|
152
|
+
style={{ fill: "var(--on-accent)" }}
|
|
153
|
+
/>
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
</>
|
|
157
|
+
) : phase === "rejected" ? (
|
|
158
|
+
<>
|
|
159
|
+
<svg
|
|
160
|
+
width="14"
|
|
161
|
+
height="14"
|
|
162
|
+
viewBox="0 0 16 16"
|
|
163
|
+
style={{ flex: "none" }}
|
|
164
|
+
aria-hidden="true"
|
|
165
|
+
>
|
|
166
|
+
<rect
|
|
167
|
+
x="3.5"
|
|
168
|
+
y="7"
|
|
169
|
+
width="9"
|
|
170
|
+
height="6"
|
|
171
|
+
rx="1.5"
|
|
172
|
+
style={{ fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 }}
|
|
173
|
+
/>
|
|
174
|
+
<path
|
|
175
|
+
d="M5.5 7V5.5C5.5 4 6.5 3 8 3C9.5 3 10.5 4 10.5 5.5V7"
|
|
176
|
+
style={{ fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 }}
|
|
177
|
+
/>
|
|
178
|
+
</svg>
|
|
179
|
+
<span style={{ fontSize: "var(--text-xs)", color: "var(--muted)" }}>
|
|
180
|
+
再実行が完了して新しい下書きが届くと、承認待ちに戻り通知されます
|
|
181
|
+
</span>
|
|
182
|
+
<div style={{ flex: 1 }} />
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
className="ghost"
|
|
186
|
+
disabled
|
|
187
|
+
style={{
|
|
188
|
+
fontSize: "var(--text-sm)",
|
|
189
|
+
opacity: 0.5,
|
|
190
|
+
whiteSpace: "nowrap",
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
差し戻す(reject)
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
className="primary"
|
|
198
|
+
disabled
|
|
199
|
+
style={{ opacity: 0.5, whiteSpace: "nowrap" }}
|
|
200
|
+
>
|
|
201
|
+
承認して次へ
|
|
202
|
+
</button>
|
|
203
|
+
</>
|
|
204
|
+
) : (
|
|
205
|
+
<>
|
|
206
|
+
<span
|
|
207
|
+
style={{
|
|
208
|
+
color:
|
|
209
|
+
phase === "failed"
|
|
210
|
+
? "var(--fail)"
|
|
211
|
+
: phase === "cancelled"
|
|
212
|
+
? "var(--muted)"
|
|
213
|
+
: "var(--ok)",
|
|
214
|
+
fontSize: "var(--text-sm)",
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
{phase === "failed" ? "✕" : phase === "cancelled" ? "◼" : "✓"}
|
|
218
|
+
</span>
|
|
219
|
+
<span style={{ fontSize: "var(--text-sm)", color: "var(--muted)" }}>
|
|
220
|
+
{phase === "failed"
|
|
221
|
+
? (decision.failedReason ?? "この step は失敗しました")
|
|
222
|
+
: phase === "cancelled"
|
|
223
|
+
? "キャンセル済み — この run は中断されました"
|
|
224
|
+
: "対応済み — 承認して次のノードへ渡しました"}
|
|
225
|
+
</span>
|
|
226
|
+
<div style={{ flex: 1 }} />
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
className="ghost"
|
|
230
|
+
onClick={() => open(actions.close())}
|
|
231
|
+
style={{ fontSize: "var(--text-sm)", whiteSpace: "nowrap" }}
|
|
232
|
+
>
|
|
233
|
+
run の進行を見る →
|
|
234
|
+
</button>
|
|
235
|
+
</>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createContext, useContext } from "react"
|
|
2
|
+
|
|
3
|
+
// 承認ビューからホスト(mawaru の承認画面)に頼む口。提供(Provider)はホストと iframe の中の実行部分だけが使うので
|
|
4
|
+
// host.ts から出し、ビューを書く人向けの approval.ts からは型と useApprovalActions だけを出す
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ビューから承認画面の操作を頼む口。決定の送信はホストが行う。
|
|
8
|
+
* ホストは、この step で・見ている人が決定できる(`ctx.decision.canDecide`)ときだけ受け、
|
|
9
|
+
* `decide` はさらに人がビューの中で押した直後のときだけ受ける(開いただけで決定するビューは書けない)。
|
|
10
|
+
*/
|
|
11
|
+
export type ApprovalActions = {
|
|
12
|
+
/** 決定する。`port` は `approve` / `reject` / 追加の出口の key。送り終わると承認画面は run の画面へ戻る */
|
|
13
|
+
decide: (port: string, value: unknown) => Promise<void>
|
|
14
|
+
/** ホストの差し戻しダイアログを開く(理由と参考ファイルを入れて差し戻す) */
|
|
15
|
+
openRejectDialog: () => Promise<void>
|
|
16
|
+
/** 追加の出口に流す値を入れるダイアログを開く(`value` が初期値)。値の型が出口と合わないときに使う */
|
|
17
|
+
openExitDialog: (key: string, value: unknown) => Promise<void>
|
|
18
|
+
/** 承認画面を閉じて run の画面へ戻る(右上の × と同じ) */
|
|
19
|
+
close: () => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ApprovalActionsContext = createContext<ApprovalActions | null>(null)
|
|
23
|
+
|
|
24
|
+
export const ApprovalActionsProvider = ApprovalActionsContext.Provider
|
|
25
|
+
|
|
26
|
+
/** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
|
|
27
|
+
export const useApprovalActions = (): ApprovalActions => {
|
|
28
|
+
const actions = useContext(ApprovalActionsContext)
|
|
29
|
+
if (!actions) throw new Error("承認画面の中で使ってください")
|
|
30
|
+
return actions
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext } from "react"
|
|
2
|
+
import type { FileRef } from "./_parts/fileRef.js"
|
|
3
|
+
|
|
4
|
+
// ビューがホスト(mawaru の画面)に頼めること。顧客 repo のビューは sandbox iframe の中で動き、
|
|
5
|
+
// API を叩けない(認証が付かない)ので、要るものはここ経由でホストに頼む
|
|
6
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「ビューがホストに頼めること」)。
|
|
7
|
+
// ホストの画面の中では API を直接呼ぶ実装を、iframe の中では postMessage の実装を差す。
|
|
8
|
+
// 部品(FilePreview・SchemaForm のファイル欄)もこれを使うので、どちらで描いても同じに動く
|
|
9
|
+
export type ViewBridge = {
|
|
10
|
+
// Storage の署名付き URL(private バケットなので描画のたびに取る)
|
|
11
|
+
fileUrl: (file: FileRef) => Promise<string>
|
|
12
|
+
// ファイルを上げて参照を返す(SchemaForm のファイル欄と同じアップロード先)
|
|
13
|
+
uploadFile: (file: File) => Promise<FileRef>
|
|
14
|
+
// クリップボードに書く(sandbox の中からは書けない)
|
|
15
|
+
copyText: (text: string) => Promise<void>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ViewBridgeContext = createContext<ViewBridge | null>(null)
|
|
19
|
+
|
|
20
|
+
export const ViewBridgeProvider = ({
|
|
21
|
+
bridge,
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
bridge: ViewBridge
|
|
25
|
+
children: ReactNode
|
|
26
|
+
}) => (
|
|
27
|
+
<ViewBridgeContext.Provider value={bridge}>
|
|
28
|
+
{children}
|
|
29
|
+
</ViewBridgeContext.Provider>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export const useViewBridge = (): ViewBridge => {
|
|
33
|
+
const bridge = useContext(ViewBridgeContext)
|
|
34
|
+
if (!bridge) throw new Error("ViewBridgeProvider の中で使ってください")
|
|
35
|
+
return bridge
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CSSProperties } from "react"
|
|
2
|
+
|
|
3
|
+
// 承認画面の「書類」カードの枠(標準ビュー・回答レビュー・お問い合わせ回答で同じ形)。
|
|
4
|
+
// 狭い幅(useNarrow)では左右の余白を詰めて本文の幅を確保する
|
|
5
|
+
export const documentCardStyle = (narrow: boolean): CSSProperties => ({
|
|
6
|
+
maxWidth: "720px",
|
|
7
|
+
margin: "0 auto",
|
|
8
|
+
display: "flex",
|
|
9
|
+
flexDirection: "column",
|
|
10
|
+
gap: "var(--space-4)",
|
|
11
|
+
background: "var(--surface)",
|
|
12
|
+
border: "var(--border-w) solid var(--border)",
|
|
13
|
+
borderRadius: "var(--radius-m)",
|
|
14
|
+
boxShadow: "var(--shadow)",
|
|
15
|
+
padding: narrow
|
|
16
|
+
? "var(--space-5) var(--space-4) var(--space-6)"
|
|
17
|
+
: "var(--space-6) var(--space-10) var(--space-8)",
|
|
18
|
+
})
|