@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { HumanDeclaration } from "./human-manifest.js";
|
|
2
3
|
import type { GraphPort, JsonSchema } from "./node.js";
|
|
3
4
|
export declare const UNDECLARED_SCHEMA: JsonSchema;
|
|
4
5
|
export declare const isUndeclaredSchema: (schema: JsonSchema) => boolean;
|
|
@@ -55,6 +56,9 @@ type PropagationNode = {
|
|
|
55
56
|
id: string;
|
|
56
57
|
kind: string;
|
|
57
58
|
ports: GraphPort[];
|
|
59
|
+
human?: {
|
|
60
|
+
declaration?: HumanDeclaration;
|
|
61
|
+
} | null;
|
|
58
62
|
};
|
|
59
63
|
type PortLink = {
|
|
60
64
|
from_port_id: string;
|
|
@@ -62,15 +66,10 @@ type PortLink = {
|
|
|
62
66
|
};
|
|
63
67
|
export declare const COPY_UPSTREAM_SCHEMA = "T";
|
|
64
68
|
export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA;
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
reject: JsonSchema;
|
|
70
|
-
} | null | undefined;
|
|
71
|
-
};
|
|
72
|
-
export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[], options?: PropagationOptions<N>) => Map<string, JsonSchema>;
|
|
73
|
-
export declare const propagateDerivedPorts: <N extends PropagationNode>(nodes: N[], connections: PortLink[], options?: PropagationOptions<N>) => N[];
|
|
69
|
+
export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => Map<string, JsonSchema>;
|
|
70
|
+
export declare const resolveFlexiblePorts: (nodes: PropagationNode[], connections: PortLink[]) => Set<string>;
|
|
71
|
+
export declare const connectionAccepted: (from: JsonSchema, to: Pick<GraphPort, "schema" | "flexible">) => boolean;
|
|
72
|
+
export declare const propagateDerivedPorts: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => N[];
|
|
74
73
|
export type SchemaFit = "ok" | "mismatch" | "unknown";
|
|
75
74
|
export declare const schemaFit: (sub: JsonSchema, sup: JsonSchema) => SchemaFit;
|
|
76
75
|
export declare const isSchemaSubset: (sub: JsonSchema, sup: JsonSchema) => 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
|
|
3
|
+
// 型の決まり方は2分類:宣言(program / ai の in・out。human は実装(nodes/human/)の宣言を転写)/
|
|
4
4
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
5
5
|
// mawaru がコードで型を固定するポートは無い
|
|
6
6
|
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
@@ -222,7 +222,7 @@ export const kindPortsViolation = (kind, ports) => {
|
|
|
222
222
|
// ---- 型伝播(導出ポートの解決) ----
|
|
223
223
|
// 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
|
|
224
224
|
// wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
|
|
225
|
-
// human
|
|
225
|
+
// human は実装(nodes/human/)の宣言を転写("T" は素通しと同じ挙動。reject もビューの宣言)。
|
|
226
226
|
// 宣言 out(program / ai)は触らない。
|
|
227
227
|
// program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
228
228
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
@@ -294,7 +294,7 @@ const isDerivedPort = (kind, port) => DERIVED_KINDS.has(kind) &&
|
|
|
294
294
|
// ai の in は nodes/ai/<dir>/config.json の inputs[key].schema 由来の宣言型が初期値で、
|
|
295
295
|
// 接続時は program と同じ鏡映で接続元と同値に同期される(どの in も対等。
|
|
296
296
|
// instruction への型の焼き込みは廃止)
|
|
297
|
-
export const resolvePortSchemas = (nodes, connections
|
|
297
|
+
export const resolvePortSchemas = (nodes, connections) => {
|
|
298
298
|
const resolved = new Map();
|
|
299
299
|
const kindByPortId = new Map();
|
|
300
300
|
for (const node of nodes) {
|
|
@@ -382,6 +382,9 @@ export const resolvePortSchemas = (nodes, connections, options) => {
|
|
|
382
382
|
for (const port of node.ports) {
|
|
383
383
|
if (port.io !== "in")
|
|
384
384
|
continue;
|
|
385
|
+
// flexible な in は宣言(推奨型)を維持する(接続の型を問わない口)
|
|
386
|
+
if (port.flexible === true)
|
|
387
|
+
continue;
|
|
385
388
|
const sources = inbound.get(port.id) ?? [];
|
|
386
389
|
const inSchema = mergeInboundSchemas(sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
|
|
387
390
|
if (!isUndeclaredSchema(inSchema))
|
|
@@ -395,13 +398,13 @@ export const resolvePortSchemas = (nodes, connections, options) => {
|
|
|
395
398
|
continue;
|
|
396
399
|
const sources = inbound.get(inPort.id) ?? [];
|
|
397
400
|
const inSchema = mergeInboundSchemas(sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
|
|
398
|
-
//
|
|
401
|
+
// 実装(nodes/human/<dir>/)の宣言を human のポートへそのまま転写する(上流ノードの kind は見ない)。
|
|
399
402
|
// "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
|
|
400
403
|
// のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
|
|
401
404
|
// 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
|
|
402
405
|
// 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
|
|
403
406
|
// ズレは接続の型エラーで見せてユーザーの [修正する] で直す
|
|
404
|
-
const declared =
|
|
407
|
+
const declared = node.kind === "human" ? node.human?.declaration : undefined;
|
|
405
408
|
const effectiveIn = declared && declared.in !== COPY_UPSTREAM_SCHEMA
|
|
406
409
|
? declared.in
|
|
407
410
|
: inSchema;
|
|
@@ -426,16 +429,68 @@ export const resolvePortSchemas = (nodes, connections, options) => {
|
|
|
426
429
|
};
|
|
427
430
|
// ノード配列へ伝播結果を書き戻す(接続変更時のポートデータ自動更新)。
|
|
428
431
|
// schema が変わらないポート・ノードは同じ参照を返す
|
|
429
|
-
|
|
430
|
-
|
|
432
|
+
// flexible の導出:start の in / out main は、接続先の in が1つ以上あり**全て** flexible
|
|
433
|
+
// (AI の宣言 in・契約で焼き込まれた component の in)のときだけ flexible。
|
|
434
|
+
// 非 flexible の接続先(program 等)が1つでも混ざれば型どおりの入力が要るので false。
|
|
435
|
+
// 直結のみ(wait 等の素通し経由は型と同じく流さない)。戻りは flexible になる port_id の集合
|
|
436
|
+
export const resolveFlexiblePorts = (nodes, connections) => {
|
|
437
|
+
const flexibleByPortId = new Map();
|
|
438
|
+
for (const node of nodes) {
|
|
439
|
+
if (node.kind === "start")
|
|
440
|
+
continue;
|
|
441
|
+
for (const port of node.ports) {
|
|
442
|
+
if (port.io === "in")
|
|
443
|
+
flexibleByPortId.set(port.id, port.flexible === true);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const outbound = new Map();
|
|
447
|
+
for (const conn of connections) {
|
|
448
|
+
const outs = outbound.get(conn.from_port_id) ?? [];
|
|
449
|
+
outs.push(conn.to_port_id);
|
|
450
|
+
outbound.set(conn.from_port_id, outs);
|
|
451
|
+
}
|
|
452
|
+
const flexible = new Set();
|
|
453
|
+
for (const node of nodes) {
|
|
454
|
+
if (node.kind !== "start")
|
|
455
|
+
continue;
|
|
456
|
+
const inPort = node.ports.find((p) => p.io === "in");
|
|
457
|
+
const outMain = node.ports.find((p) => p.io === "out" && p.key === "main");
|
|
458
|
+
if (!inPort || !outMain)
|
|
459
|
+
continue;
|
|
460
|
+
const targets = outbound.get(outMain.id) ?? [];
|
|
461
|
+
if (targets.length > 0 &&
|
|
462
|
+
targets.every((id) => flexibleByPortId.get(id) === true)) {
|
|
463
|
+
flexible.add(inPort.id);
|
|
464
|
+
flexible.add(outMain.id);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return flexible;
|
|
468
|
+
};
|
|
469
|
+
// 接続の型が受け入れられるか:受け側が flexible なら常に可、それ以外は 伝播型 ⊆ 宣言。
|
|
470
|
+
// sdk の保存契約・backend の保存時チェック・エディタの表示はすべてこれを使う
|
|
471
|
+
// (判定が散ると表示と保存がズレる)
|
|
472
|
+
export const connectionAccepted = (from, to) => to.flexible === true || isSchemaSubset(from, to.schema);
|
|
473
|
+
export const propagateDerivedPorts = (nodes, connections) => {
|
|
474
|
+
const resolved = resolvePortSchemas(nodes, connections);
|
|
475
|
+
const flexiblePorts = resolveFlexiblePorts(nodes, connections);
|
|
431
476
|
return nodes.map((node) => {
|
|
432
477
|
let touched = false;
|
|
433
478
|
const ports = node.ports.map((port) => {
|
|
434
479
|
const schema = resolved.get(port.id) ?? port.schema;
|
|
435
|
-
|
|
480
|
+
// start の flexible は導出結果で置き換える(false は省略=キー無し)
|
|
481
|
+
const flexible = node.kind === "start"
|
|
482
|
+
? flexiblePorts.has(port.id) || undefined
|
|
483
|
+
: port.flexible;
|
|
484
|
+
if (JSON.stringify(schema) === JSON.stringify(port.schema) &&
|
|
485
|
+
flexible === port.flexible)
|
|
436
486
|
return port;
|
|
437
487
|
touched = true;
|
|
438
|
-
|
|
488
|
+
const { flexible: _dropped, ...rest } = port;
|
|
489
|
+
return {
|
|
490
|
+
...rest,
|
|
491
|
+
schema,
|
|
492
|
+
...(flexible ? { flexible } : {}),
|
|
493
|
+
};
|
|
439
494
|
});
|
|
440
495
|
return touched ? { ...node, ports } : node;
|
|
441
496
|
});
|
package/dist/cli.js
CHANGED
|
@@ -12,14 +12,15 @@ const USAGE = `使い方: mawaru <command> [引数]
|
|
|
12
12
|
|
|
13
13
|
コマンド:
|
|
14
14
|
init repo を mawaru 実行用に初期化する(runner yml・nodes/ 骨組み・スキルポインタ等。再実行は冪等)
|
|
15
|
-
typegen nodes/program/<dir>/config.json から types.d.ts
|
|
15
|
+
typegen nodes/program/<dir>/・nodes/human/<dir>/ の config.json から types.d.ts を生成する
|
|
16
16
|
validate リポジトリ規約を検証する(error があれば exit 1。CI 向け)
|
|
17
17
|
login ブラウザで承認して mawaru にログインする(~/.mawaru/credentials.json)
|
|
18
18
|
logout 保存した資格情報を消す
|
|
19
19
|
session resume <step URL>
|
|
20
20
|
AI ノードのセッションを手元に復元して CLI(claude / codex)の resume で再開する
|
|
21
21
|
preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]
|
|
22
|
-
|
|
22
|
+
push せずに手元のファイルで確かめる(human:nodes/human/ の承認画面をブラウザで開く/
|
|
23
|
+
slack:views/slack/ のビューで cases.ts のケースを Slack に送る)`;
|
|
23
24
|
const [command, ...rest] = process.argv.slice(2);
|
|
24
25
|
// init / typegen / validate は repo ルートを任意で受ける(省略時はカレント)
|
|
25
26
|
const root = rest[0] ?? process.cwd();
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./_schemas/duplicate-selection.js";
|
|
|
3
3
|
export * from "./_schemas/extract-component.js";
|
|
4
4
|
export * from "./_schemas/graph.js";
|
|
5
5
|
export * from "./_schemas/hook-manifest.js";
|
|
6
|
+
export * from "./_schemas/human-manifest.js";
|
|
6
7
|
export * from "./_schemas/node.js";
|
|
7
8
|
export * from "./_schemas/port-spec.js";
|
|
8
9
|
export * from "./_schemas/program-manifest.js";
|
package/dist/index.js
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/dist/init.js
CHANGED
|
@@ -68,13 +68,32 @@ export const runInit = (root) => {
|
|
|
68
68
|
// 作用してしまうため、ドット無しの "gitignore" で持つ
|
|
69
69
|
writeIfAbsent(".gitignore", template("gitignore"));
|
|
70
70
|
// package.json:@mawaru/sdk を devDependencies に(ポインタ・docs の参照解決と
|
|
71
|
-
// typegen / validate CLI
|
|
72
|
-
|
|
71
|
+
// typegen / validate CLI のため)。承認ビュー(TSX)の型のために @types/react も。
|
|
72
|
+
// バージョンはこのパッケージ自身から取る
|
|
73
|
+
const sdkPackage = JSON.parse(readFileSync(join(packageRoot(), "package.json"), "utf-8"));
|
|
73
74
|
writeIfAbsent("package.json", `${JSON.stringify({
|
|
74
75
|
name: "mawaru",
|
|
75
76
|
private: true,
|
|
76
77
|
type: "module",
|
|
77
|
-
devDependencies: {
|
|
78
|
+
devDependencies: {
|
|
79
|
+
"@mawaru/sdk": `^${sdkPackage.version}`,
|
|
80
|
+
"@types/react": sdkPackage.devDependencies["@types/react"],
|
|
81
|
+
},
|
|
82
|
+
}, null, 2)}\n`);
|
|
83
|
+
// tsconfig.json:エディタで human ノードの実装(nodes/human/)と Slack ビュー(views/)を型検査するための設定。
|
|
84
|
+
// ビルドはしない(mawaru がまとめる)。import の解決は mawaru のまとめ方(esbuild)に揃える
|
|
85
|
+
writeIfAbsent("tsconfig.json", `${JSON.stringify({
|
|
86
|
+
compilerOptions: {
|
|
87
|
+
target: "ES2022",
|
|
88
|
+
module: "ESNext",
|
|
89
|
+
moduleResolution: "Bundler",
|
|
90
|
+
jsx: "react-jsx",
|
|
91
|
+
lib: ["ES2023", "DOM", "DOM.Iterable"],
|
|
92
|
+
strict: true,
|
|
93
|
+
noEmit: true,
|
|
94
|
+
skipLibCheck: true,
|
|
95
|
+
},
|
|
96
|
+
include: ["nodes/human", "views"],
|
|
78
97
|
}, null, 2)}\n`);
|
|
79
98
|
// runner yml の `npm ci` とポインタの参照解決は node_modules / lockfile が前提
|
|
80
99
|
if (!existsSync(join(root, "package-lock.json"))) {
|
package/dist/typegen.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { type HumanDeclaration } from "./_schemas/human-manifest.js";
|
|
1
2
|
import type { JsonSchema } from "./_schemas/node.js";
|
|
2
3
|
import { type ProgramManifest } from "./_schemas/program-manifest.js";
|
|
3
4
|
export declare const CONFIG_HASH_PREFIX = "// mawaru:config-hash=";
|
|
4
5
|
export declare const configHash: (rawConfig: string) => string;
|
|
5
|
-
export declare const tsTypeFromJsonSchema: (schema: JsonSchema, indent?: number) => string;
|
|
6
|
+
export declare const tsTypeFromJsonSchema: (schema: JsonSchema, indent?: number, fileAs?: string) => string;
|
|
6
7
|
export declare const renderTypesFile: (manifest: ProgramManifest, rawConfig: string) => string;
|
|
8
|
+
export declare const renderApprovalViewTypesFile: (declaration: HumanDeclaration, rawConfig: string) => string;
|
|
7
9
|
export type TypegenResult = {
|
|
8
10
|
written: string[];
|
|
9
11
|
errors: {
|
package/dist/typegen.js
CHANGED
|
@@ -2,6 +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
6
|
import { programManifestSchema, } from "./_schemas/program-manifest.js";
|
|
6
7
|
// 生成ファイルに刻む config.json 内容のハッシュ。validate が drift(config を変えて
|
|
7
8
|
// typegen し忘れ)の検出に使う
|
|
@@ -12,14 +13,14 @@ const propertyKey = (key) => IDENTIFIER_RE.test(key) ? key : JSON.stringify(key)
|
|
|
12
13
|
const literalType = (value) => value === null ? "null" : (JSON.stringify(value) ?? "unknown");
|
|
13
14
|
// union / tuple の要素は結合子と混ざらないよう括弧で包む
|
|
14
15
|
const wrapIfUnion = (type) => type.includes(" | ") || type.includes(" & ") ? `(${type})` : type;
|
|
15
|
-
const objectType = (schema, indent) => {
|
|
16
|
+
const objectType = (schema, indent, fileAs) => {
|
|
16
17
|
const properties = schema.properties;
|
|
17
18
|
if (typeof properties !== "object" ||
|
|
18
19
|
properties === null ||
|
|
19
20
|
Array.isArray(properties)) {
|
|
20
21
|
const additional = schema.additionalProperties;
|
|
21
22
|
if (typeof additional === "object" && additional !== null) {
|
|
22
|
-
return `Record<string, ${tsTypeFromJsonSchema(additional, indent)}>`;
|
|
23
|
+
return `Record<string, ${tsTypeFromJsonSchema(additional, indent, fileAs)}>`;
|
|
23
24
|
}
|
|
24
25
|
if (additional === false) {
|
|
25
26
|
return "Record<string, never>";
|
|
@@ -39,14 +40,14 @@ const objectType = (schema, indent) => {
|
|
|
39
40
|
lines.push(`${inner}/** ${description} */`);
|
|
40
41
|
}
|
|
41
42
|
const optional = required.has(key) ? "" : "?";
|
|
42
|
-
lines.push(`${inner}${propertyKey(key)}${optional}: ${tsTypeFromJsonSchema(propSchema, indent + 1)}`);
|
|
43
|
+
lines.push(`${inner}${propertyKey(key)}${optional}: ${tsTypeFromJsonSchema(propSchema, indent + 1, fileAs)}`);
|
|
43
44
|
}
|
|
44
45
|
if (lines.length === 0) {
|
|
45
46
|
return "Record<string, unknown>";
|
|
46
47
|
}
|
|
47
48
|
return `{\n${lines.join("\n")}\n${" ".repeat(indent)}}`;
|
|
48
49
|
};
|
|
49
|
-
const namedType = (typeName, schema, indent) => {
|
|
50
|
+
const namedType = (typeName, schema, indent, fileAs) => {
|
|
50
51
|
switch (typeName) {
|
|
51
52
|
case "string":
|
|
52
53
|
return "string";
|
|
@@ -60,22 +61,24 @@ const namedType = (typeName, schema, indent) => {
|
|
|
60
61
|
case "array": {
|
|
61
62
|
const items = schema.items;
|
|
62
63
|
if (Array.isArray(items)) {
|
|
63
|
-
return `[${items.map((i) => tsTypeFromJsonSchema(i, indent)).join(", ")}]`;
|
|
64
|
+
return `[${items.map((i) => tsTypeFromJsonSchema(i, indent, fileAs)).join(", ")}]`;
|
|
64
65
|
}
|
|
65
66
|
if (typeof items === "object" && items !== null) {
|
|
66
|
-
return `${wrapIfUnion(tsTypeFromJsonSchema(items, indent))}[]`;
|
|
67
|
+
return `${wrapIfUnion(tsTypeFromJsonSchema(items, indent, fileAs))}[]`;
|
|
67
68
|
}
|
|
68
69
|
return "unknown[]";
|
|
69
70
|
}
|
|
70
71
|
case "object":
|
|
71
|
-
return objectType(schema, indent);
|
|
72
|
+
return objectType(schema, indent, fileAs);
|
|
72
73
|
default:
|
|
73
74
|
return "unknown";
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
// JSON Schema(zodFromJsonSchema が扱うサブセット)を TS 型の文字列に写す。
|
|
77
78
|
// 解釈できない形は unknown に落とす(型が付かないだけで、実行時検証は ports 側が担う)
|
|
78
|
-
|
|
79
|
+
// fileAs=file 型の TS 型。program の handler は手元のパス(string)を受ける。承認ビューは参照
|
|
80
|
+
// (FileRef)のまま受ける
|
|
81
|
+
export const tsTypeFromJsonSchema = (schema, indent = 0, fileAs = "string") => {
|
|
79
82
|
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
|
80
83
|
return "unknown";
|
|
81
84
|
}
|
|
@@ -84,7 +87,7 @@ export const tsTypeFromJsonSchema = (schema, indent = 0) => {
|
|
|
84
87
|
// 出力にローカルパスを書く契約なので、参照 object ではなく string を生成する
|
|
85
88
|
// (AI 側の schemaWithFilesAsPath と同じ規約)
|
|
86
89
|
if (schema["x-mawaru-type"] === "file") {
|
|
87
|
-
return
|
|
90
|
+
return fileAs;
|
|
88
91
|
}
|
|
89
92
|
if ("const" in schema) {
|
|
90
93
|
return literalType(schema.const);
|
|
@@ -97,22 +100,24 @@ export const tsTypeFromJsonSchema = (schema, indent = 0) => {
|
|
|
97
100
|
const variants = schema.oneOf ?? schema.anyOf;
|
|
98
101
|
if (Array.isArray(variants) && variants.length > 0) {
|
|
99
102
|
return variants
|
|
100
|
-
.map((v) => tsTypeFromJsonSchema(v, indent))
|
|
103
|
+
.map((v) => tsTypeFromJsonSchema(v, indent, fileAs))
|
|
101
104
|
.join(" | ");
|
|
102
105
|
}
|
|
103
106
|
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
|
104
107
|
return schema.allOf
|
|
105
|
-
.map((v) => wrapIfUnion(tsTypeFromJsonSchema(v, indent)))
|
|
108
|
+
.map((v) => wrapIfUnion(tsTypeFromJsonSchema(v, indent, fileAs)))
|
|
106
109
|
.join(" & ");
|
|
107
110
|
}
|
|
108
111
|
const type = schema.type;
|
|
109
112
|
if (Array.isArray(type)) {
|
|
110
113
|
return type.length > 0
|
|
111
|
-
? type
|
|
114
|
+
? type
|
|
115
|
+
.map((t) => namedType(String(t), schema, indent, fileAs))
|
|
116
|
+
.join(" | ")
|
|
112
117
|
: "unknown";
|
|
113
118
|
}
|
|
114
119
|
if (typeof type === "string") {
|
|
115
|
-
return namedType(type, schema, indent);
|
|
120
|
+
return namedType(type, schema, indent, fileAs);
|
|
116
121
|
}
|
|
117
122
|
return "unknown";
|
|
118
123
|
};
|
|
@@ -166,8 +171,85 @@ export const renderTypesFile = (manifest, rawConfig) => {
|
|
|
166
171
|
"",
|
|
167
172
|
].join("\n");
|
|
168
173
|
};
|
|
169
|
-
// nodes/
|
|
174
|
+
// human ノードの実装(nodes/human/<dir>/config.json)の型。ビューの ctx の型(defineApprovalView<In>)と、
|
|
175
|
+
// 承認バーで approve に流す値の型(<ApprovalBar<Approve> output={…} />)に使う。
|
|
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"));
|
|
183
|
+
return [
|
|
184
|
+
"// このファイルは mawaru typegen が config.json から自動生成した(手で編集しない)",
|
|
185
|
+
`${CONFIG_HASH_PREFIX}${configHash(rawConfig)}`,
|
|
186
|
+
"",
|
|
187
|
+
...(usesFileRef
|
|
188
|
+
? ['import type { FileRef } from "@mawaru/sdk/human"', ""]
|
|
189
|
+
: []),
|
|
190
|
+
"// 承認待ちの値(ctx.output)。inputs.in を省略した(上流の型をそのまま受ける)なら unknown",
|
|
191
|
+
`export type In = ${inType}`,
|
|
192
|
+
"",
|
|
193
|
+
"// 承認で approve に載せる値(ApprovalBar の output)。省略したなら In と同じ",
|
|
194
|
+
`export type Approve = ${approveType}`,
|
|
195
|
+
"",
|
|
196
|
+
"// 差し戻しで reject に載せる値",
|
|
197
|
+
`export type Reject = ${rejectType}`,
|
|
198
|
+
"",
|
|
199
|
+
].join("\n");
|
|
200
|
+
};
|
|
201
|
+
// nodes/human/ 直下の各 human ノードの実装(`_` 始まりは部品の置き場なので除く)の config.json から types.d.ts を生成する
|
|
202
|
+
const runHumanTypegen = (root) => {
|
|
203
|
+
const viewsDir = join(root, "nodes", "human");
|
|
204
|
+
const written = [];
|
|
205
|
+
const errors = [];
|
|
206
|
+
if (!existsSync(viewsDir))
|
|
207
|
+
return { written, errors };
|
|
208
|
+
for (const entry of readdirSync(viewsDir, { withFileTypes: true })) {
|
|
209
|
+
if (!entry.isDirectory() ||
|
|
210
|
+
entry.name.startsWith("_") ||
|
|
211
|
+
entry.name.startsWith(".")) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const dir = `nodes/human/${entry.name}`;
|
|
215
|
+
const configPath = join(viewsDir, entry.name, "config.json");
|
|
216
|
+
if (!existsSync(configPath)) {
|
|
217
|
+
errors.push({ dir, message: "config.json がありません" });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const rawConfig = readFileSync(configPath, "utf8");
|
|
221
|
+
let parsed;
|
|
222
|
+
try {
|
|
223
|
+
parsed = JSON.parse(rawConfig);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
errors.push({ dir, message: "config.json が JSON として不正です" });
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const config = humanManifestSchema.safeParse(parsed);
|
|
230
|
+
if (!config.success) {
|
|
231
|
+
errors.push({
|
|
232
|
+
dir,
|
|
233
|
+
message: `config.json が規約に合いません(${config.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(" / ")})`,
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const typesPath = join(viewsDir, entry.name, "types.d.ts");
|
|
238
|
+
writeFileSync(typesPath, renderApprovalViewTypesFile(humanDeclarationOfManifest(config.data), rawConfig));
|
|
239
|
+
written.push(typesPath);
|
|
240
|
+
}
|
|
241
|
+
return { written, errors };
|
|
242
|
+
};
|
|
243
|
+
// nodes/program/ 直下の各 dir と、nodes/human/ 直下の各実装の config.json から types.d.ts を生成する
|
|
170
244
|
export const runTypegen = (root) => {
|
|
245
|
+
const programs = runProgramTypegen(root);
|
|
246
|
+
const views = runHumanTypegen(root);
|
|
247
|
+
return {
|
|
248
|
+
written: [...programs.written, ...views.written],
|
|
249
|
+
errors: [...programs.errors, ...views.errors],
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
const runProgramTypegen = (root) => {
|
|
171
253
|
const programsDir = join(root, "nodes", "program");
|
|
172
254
|
if (!existsSync(programsDir)) {
|
|
173
255
|
return {
|
package/dist/validate.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { convertJsonSchemaToZod } from "zod-from-json-schema";
|
|
5
|
+
import { humanManifestSchema } from "./_schemas/human-manifest.js";
|
|
5
6
|
import { programManifestSchema } from "./_schemas/program-manifest.js";
|
|
6
7
|
import { CONFIG_HASH_PREFIX, configHash } from "./typegen.js";
|
|
7
8
|
// エディタ・オーケストレータは zod-from-json-schema(common の zodFromJsonSchema)で
|
|
@@ -101,6 +102,59 @@ const validateProgram = (programsDir, dir, root) => {
|
|
|
101
102
|
checkTypegenDrift(programDir, rawConfig, path, issues);
|
|
102
103
|
return issues;
|
|
103
104
|
};
|
|
105
|
+
// human ノードの実装(nodes/human/<dir>/):config.json(宣言の検査)・index.tsx・types.d.ts の drift。
|
|
106
|
+
// `_` 始まりは部品の置き場なので見ない
|
|
107
|
+
const validateHumans = (root) => {
|
|
108
|
+
const issues = [];
|
|
109
|
+
const viewsDir = join(root, "nodes", "human");
|
|
110
|
+
if (!existsSync(viewsDir))
|
|
111
|
+
return issues;
|
|
112
|
+
for (const entry of readdirSync(viewsDir, { withFileTypes: true })) {
|
|
113
|
+
if (!entry.isDirectory() ||
|
|
114
|
+
entry.name.startsWith("_") ||
|
|
115
|
+
entry.name.startsWith(".")) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const viewDir = join(viewsDir, entry.name);
|
|
119
|
+
const path = relative(root, viewDir);
|
|
120
|
+
if (!existsSync(join(viewDir, "index.tsx"))) {
|
|
121
|
+
issues.push({ level: "error", path, message: "index.tsx がありません" });
|
|
122
|
+
}
|
|
123
|
+
const configPath = join(viewDir, "config.json");
|
|
124
|
+
if (!existsSync(configPath)) {
|
|
125
|
+
issues.push({ level: "error", path, message: "config.json がありません" });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const rawConfig = readFileSync(configPath, "utf8");
|
|
129
|
+
let parsed;
|
|
130
|
+
try {
|
|
131
|
+
parsed = JSON.parse(rawConfig);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
issues.push({
|
|
135
|
+
level: "error",
|
|
136
|
+
path: `${path}/config.json`,
|
|
137
|
+
message: "JSON として不正です",
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const config = humanManifestSchema.safeParse(parsed);
|
|
142
|
+
if (!config.success) {
|
|
143
|
+
for (const issue of config.error.issues) {
|
|
144
|
+
issues.push({
|
|
145
|
+
level: "error",
|
|
146
|
+
path: `${path}/config.json`,
|
|
147
|
+
message: `${issue.path.join(".")}: ${issue.message}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (existsSync(join(viewDir, "types.d.ts"))) {
|
|
153
|
+
checkTypegenDrift(viewDir, rawConfig, path, issues);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return issues;
|
|
157
|
+
};
|
|
104
158
|
// リポジトリ規約の構造検証。error が1つでもあれば CI を落とす想定(cli.ts が exit 1)
|
|
105
159
|
export const validateRepo = (root) => {
|
|
106
160
|
const issues = [];
|
|
@@ -119,6 +173,7 @@ export const validateRepo = (root) => {
|
|
|
119
173
|
}
|
|
120
174
|
}
|
|
121
175
|
}
|
|
176
|
+
issues.push(...validateHumans(root));
|
|
122
177
|
if (!existsSync(join(root, ".github", "workflows", "mawaru-runner.yml"))) {
|
|
123
178
|
issues.push({
|
|
124
179
|
level: "warning",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ApprovalViewContext } from "../human.js";
|
|
3
|
+
/**
|
|
4
|
+
* 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
|
|
5
|
+
* 中身を `flex: 1` にすると、中身が短くても下端に来る。
|
|
6
|
+
*/
|
|
7
|
+
export declare const ApprovalBar: <TOut = unknown>({ ctx, label, disabled, output, value, children, }: {
|
|
8
|
+
/** ビューが受け取った ctx */
|
|
9
|
+
ctx: ApprovalViewContext<unknown>;
|
|
10
|
+
/** 承認ボタンの文言(省略時は「承認して〈次のノード〉へ」) */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** 承認ボタンを押せない */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** 承認したとき approve に流す値(宣言の outputs.approve の形。省略時は value)。`<ApprovalBar<Approve> …>` で型を検査できる */
|
|
15
|
+
output?: TOut;
|
|
16
|
+
/** 今の値(ビューで直した値。in の形。省略時は ctx.output)。型の合う追加の出口にはこれが流れる */
|
|
17
|
+
value?: unknown;
|
|
18
|
+
/** バーの左側に足すもの */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { useApprovalActions } from "../_approvalActions.js";
|
|
4
|
+
// 承認バー(承認画面の下の帯)。ビューが自分で置く部品で、内蔵の標準ビューも同じものを使う
|
|
5
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
|
|
6
|
+
// 材料は ctx.decision、操作は useApprovalActions()。送信中とエラーは decide の結果からここで持つ
|
|
7
|
+
const errorMessage = (err) => err instanceof Error ? err.message : "送信に失敗しました";
|
|
8
|
+
/**
|
|
9
|
+
* 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
|
|
10
|
+
* 中身を `flex: 1` にすると、中身が短くても下端に来る。
|
|
11
|
+
*/
|
|
12
|
+
export const ApprovalBar = ({ ctx, label, disabled = false, output, value, children, }) => {
|
|
13
|
+
const actions = useApprovalActions();
|
|
14
|
+
const [sending, setSending] = useState(false);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const { phase, decision } = ctx;
|
|
17
|
+
const current = value === undefined ? ctx.output : value;
|
|
18
|
+
const approveValue = output === undefined ? current : output;
|
|
19
|
+
const send = (request) => {
|
|
20
|
+
setSending(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
request
|
|
23
|
+
.catch((err) => setError(errorMessage(err)))
|
|
24
|
+
.finally(() => setSending(false));
|
|
25
|
+
};
|
|
26
|
+
const open = (request) => request.catch((err) => setError(errorMessage(err)));
|
|
27
|
+
return (_jsx("div", { style: {
|
|
28
|
+
position: "sticky",
|
|
29
|
+
bottom: 0,
|
|
30
|
+
zIndex: 1,
|
|
31
|
+
flex: "none",
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
gap: "var(--space-3)",
|
|
35
|
+
padding: "var(--space-3) var(--space-6)",
|
|
36
|
+
borderTop: phase === "waiting"
|
|
37
|
+
? "var(--border-w-strong) solid var(--human)"
|
|
38
|
+
: "var(--border-w) solid var(--border)",
|
|
39
|
+
background: "var(--surface)",
|
|
40
|
+
}, children: phase === "waiting" ? (_jsxs(_Fragment, { children: [decision.otherAssignees && (_jsxs("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: [_jsx("span", { style: { color: "var(--ink)" }, children: decision.otherAssignees.join("・") }), " さんの担当です — あなたは承認・差し戻しができません"] })), children, _jsx("div", { style: { flex: 1 } }), decision.progress && (_jsxs("span", { style: {
|
|
41
|
+
fontFamily: "var(--font-mono)",
|
|
42
|
+
fontSize: "var(--text-xs)",
|
|
43
|
+
color: "var(--muted)",
|
|
44
|
+
whiteSpace: "nowrap",
|
|
45
|
+
}, children: [decision.progress.done, "/", decision.progress.total, "\u4EBA \u627F\u8A8D\u6E08\u307F"] })), error && (_jsx("span", { role: "alert", style: { color: "var(--fail)" }, children: error })), decision.exits.map((exit) => (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => exit.needsInput
|
|
46
|
+
? open(actions.openExitDialog(exit.key, current))
|
|
47
|
+
: send(actions.decide(exit.key, current)), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: exit.name }, exit.key))), decision.rejectTargetName && (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => open(actions.openRejectDialog()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "AI\u3078\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" })), _jsxs("button", { type: "button", className: "primary", disabled: !decision.canDecide || sending || disabled, onClick: () => send(actions.decide("approve", approveValue)), style: {
|
|
48
|
+
display: "flex",
|
|
49
|
+
alignItems: "center",
|
|
50
|
+
gap: "var(--space-2)",
|
|
51
|
+
paddingLeft: "var(--space-5)",
|
|
52
|
+
paddingRight: "var(--space-5)",
|
|
53
|
+
whiteSpace: "nowrap",
|
|
54
|
+
}, children: [sending
|
|
55
|
+
? "承認しています…"
|
|
56
|
+
: decision.progress?.approvedByMe
|
|
57
|
+
? "承認済み — 全員の承認待ち"
|
|
58
|
+
: (label ?? `承認して${decision.approveTargetName ?? "次"}へ`), _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "M4.5 3L12.5 8L4.5 13V3Z", style: { fill: "var(--on-accent)" } }) })] })] })) : phase === "rejected" ? (_jsxs(_Fragment, { children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", style: { flex: "none" }, "aria-hidden": "true", children: [_jsx("rect", { x: "3.5", y: "7", width: "9", height: "6", rx: "1.5", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } }), _jsx("path", { d: "M5.5 7V5.5C5.5 4 6.5 3 8 3C9.5 3 10.5 4 10.5 5.5V7", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } })] }), _jsx("span", { style: { fontSize: "var(--text-xs)", color: "var(--muted)" }, children: "\u518D\u5B9F\u884C\u304C\u5B8C\u4E86\u3057\u3066\u65B0\u3057\u3044\u4E0B\u66F8\u304D\u304C\u5C4A\u304F\u3068\u3001\u627F\u8A8D\u5F85\u3061\u306B\u623B\u308A\u901A\u77E5\u3055\u308C\u307E\u3059" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", disabled: true, style: {
|
|
59
|
+
fontSize: "var(--text-sm)",
|
|
60
|
+
opacity: 0.5,
|
|
61
|
+
whiteSpace: "nowrap",
|
|
62
|
+
}, children: "\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" }), _jsx("button", { type: "button", className: "primary", disabled: true, style: { opacity: 0.5, whiteSpace: "nowrap" }, children: "\u627F\u8A8D\u3057\u3066\u6B21\u3078" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { style: {
|
|
63
|
+
color: phase === "failed"
|
|
64
|
+
? "var(--fail)"
|
|
65
|
+
: phase === "cancelled"
|
|
66
|
+
? "var(--muted)"
|
|
67
|
+
: "var(--ok)",
|
|
68
|
+
fontSize: "var(--text-sm)",
|
|
69
|
+
}, children: phase === "failed" ? "✕" : phase === "cancelled" ? "◼" : "✓" }), _jsx("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: phase === "failed"
|
|
70
|
+
? (decision.failedReason ?? "この step は失敗しました")
|
|
71
|
+
: phase === "cancelled"
|
|
72
|
+
? "キャンセル済み — この run は中断されました"
|
|
73
|
+
: "対応済み — 承認して次のノードへ渡しました" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", onClick: () => open(actions.close()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "run \u306E\u9032\u884C\u3092\u898B\u308B \u2192" })] })) }));
|
|
74
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ビューから承認画面の操作を頼む口。決定の送信はホストが行う。
|
|
3
|
+
* ホストは、この step で・見ている人が決定できる(`ctx.decision.canDecide`)ときだけ受け、
|
|
4
|
+
* `decide` はさらに人がビューの中で押した直後のときだけ受ける(開いただけで決定するビューは書けない)。
|
|
5
|
+
*/
|
|
6
|
+
export type ApprovalActions = {
|
|
7
|
+
/** 決定する。`port` は `approve` / `reject` / 追加の出口の key。送り終わると承認画面は run の画面へ戻る */
|
|
8
|
+
decide: (port: string, value: unknown) => Promise<void>;
|
|
9
|
+
/** ホストの差し戻しダイアログを開く(理由と参考ファイルを入れて差し戻す) */
|
|
10
|
+
openRejectDialog: () => Promise<void>;
|
|
11
|
+
/** 追加の出口に流す値を入れるダイアログを開く(`value` が初期値)。値の型が出口と合わないときに使う */
|
|
12
|
+
openExitDialog: (key: string, value: unknown) => Promise<void>;
|
|
13
|
+
/** 承認画面を閉じて run の画面へ戻る(右上の × と同じ) */
|
|
14
|
+
close: () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export declare const ApprovalActionsProvider: import("react").Provider<ApprovalActions | null>;
|
|
17
|
+
/** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
|
|
18
|
+
export declare const useApprovalActions: () => ApprovalActions;
|