@mawaru/sdk 0.39.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/graph.test.ts +15 -9
- package/_schemas/graph.ts +20 -6
- package/_schemas/human-manifest.test.ts +129 -0
- package/_schemas/human-manifest.ts +158 -0
- package/_schemas/port-spec.test.ts +40 -40
- package/_schemas/port-spec.ts +10 -24
- 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/graph.d.ts +21 -3
- package/dist/_schemas/graph.js +17 -6
- package/dist/_schemas/human-manifest.d.ts +27 -0
- package/dist/_schemas/human-manifest.js +121 -0
- package/dist/_schemas/port-spec.d.ts +6 -9
- package/dist/_schemas/port-spec.js +7 -7
- 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 +40 -2
- 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/_schemas/graph.test.ts
CHANGED
|
@@ -174,20 +174,26 @@ describe("humanConfigSchema", () => {
|
|
|
174
174
|
).toThrow(/担当/)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
it("承認ビューを受け付ける", () => {
|
|
177
|
+
// 実装は nodes/human/ 直下のディレクトリ名(program_dir / ai_dir と同じ)。repo にあるかは保存 API が見る
|
|
178
|
+
it("実装(human_dir・ref)を受け付ける", () => {
|
|
180
179
|
const parsed = humanConfigSchema.parse({
|
|
181
180
|
assignment: { units: [[uuid()]] },
|
|
182
|
-
|
|
181
|
+
human_dir: "invoice-review",
|
|
182
|
+
ref: "try-view",
|
|
183
183
|
})
|
|
184
|
-
expect(parsed.
|
|
184
|
+
expect(parsed.human_dir).toBe("invoice-review")
|
|
185
|
+
expect(parsed.ref).toBe("try-view")
|
|
185
186
|
})
|
|
186
187
|
|
|
187
|
-
it("
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
it("空・パスの human_dir は拒否する", () => {
|
|
189
|
+
for (const human_dir of ["", "a/b", "..", "a\\b"]) {
|
|
190
|
+
expect(() =>
|
|
191
|
+
humanConfigSchema.parse({
|
|
192
|
+
assignment: { units: [[uuid()]] },
|
|
193
|
+
human_dir,
|
|
194
|
+
}),
|
|
195
|
+
).toThrow()
|
|
196
|
+
}
|
|
191
197
|
})
|
|
192
198
|
|
|
193
199
|
it("fixed:1ユニット(複数人の and/or)を受け付ける", () => {
|
package/_schemas/graph.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
+
import { humanDeclarationSchema } from "./human-manifest.js"
|
|
2
3
|
import {
|
|
3
4
|
graphPortSchema,
|
|
4
5
|
jsonSchemaSchema,
|
|
@@ -156,10 +157,23 @@ export type AssignMember = z.infer<typeof assignMemberSchema>
|
|
|
156
157
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
157
158
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
158
159
|
export const humanConfigSchema = z.object({
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
160
|
+
// 実装(承認画面と入出力の宣言)の置き場所:実行 repo の nodes/human/ 直下のディレクトリ名。
|
|
161
|
+
// program_dir / ai_dir と同じ。省略時は mawaru 内蔵の標準の承認画面
|
|
162
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「human ノードの実装として置く」)
|
|
163
|
+
human_dir: z
|
|
164
|
+
.string()
|
|
165
|
+
.min(1)
|
|
166
|
+
.refine(
|
|
167
|
+
(v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..",
|
|
168
|
+
"human_dir は nodes/human/ 直下のディレクトリ名で指定してください",
|
|
169
|
+
)
|
|
170
|
+
.optional(),
|
|
171
|
+
// 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
|
|
172
|
+
ref: z.string().min(1).optional(),
|
|
173
|
+
// 宣言の写し(in / approve / reject の型)。伝播はこれを読む。human_dir を選んだときに
|
|
174
|
+
// 一覧 API(GET /tenants/:id/github/humans)の宣言を入れる。省略すると保存時に mawaru が repo の
|
|
175
|
+
// config.json から読む(下書きに同じ human_dir の同じノードがあればその写しを保つ)
|
|
176
|
+
declaration: humanDeclarationSchema.optional(),
|
|
163
177
|
assignment: z
|
|
164
178
|
.object({
|
|
165
179
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -293,8 +307,8 @@ export const graphNodeSchema = z.object({
|
|
|
293
307
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
294
308
|
icon: nodeIconSchema.optional(),
|
|
295
309
|
// Slack ビュー(全 kind 共通の任意設定。実行中(human は承認待ち)・完了メッセージの見せ方)。
|
|
296
|
-
// human の view と同じ「不透明なキー」で、選べる key は GET slack
|
|
297
|
-
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/slack
|
|
310
|
+
// human の view と同じ「不透明なキー」で、選べる key は GET views/slack が返す。
|
|
311
|
+
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/views/slack の契約で書く)。
|
|
298
312
|
// 保存時に mawaru が見るのは common/* の実在と <slug>/* の可視性だけ。
|
|
299
313
|
// 省略=承認待ちは承認ボタンだけ・完了は ✅ の1行(データは出さない)。
|
|
300
314
|
// ポートの型には一切影響しない
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import {
|
|
3
|
+
humanDeclarationOfManifest,
|
|
4
|
+
humanDeclarationSchema,
|
|
5
|
+
humanManifestSchema,
|
|
6
|
+
STANDARD_HUMAN_DECLARATION,
|
|
7
|
+
} from "./human-manifest.js"
|
|
8
|
+
import { fileSchema, instructionSchema } from "./port-spec.js"
|
|
9
|
+
|
|
10
|
+
const pr = {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
title: { type: "string", description: "PR のタイトル" },
|
|
14
|
+
gifs: { type: "array", items: fileSchema() },
|
|
15
|
+
count: { type: "integer" },
|
|
16
|
+
},
|
|
17
|
+
required: ["title"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const issuesOf = (value: unknown) => {
|
|
21
|
+
const parsed = humanDeclarationSchema.safeParse(value)
|
|
22
|
+
return parsed.success
|
|
23
|
+
? []
|
|
24
|
+
: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("humanDeclarationSchema(宣言の検査)", () => {
|
|
28
|
+
it("標準ビューの宣言と具体型の宣言を通す", () => {
|
|
29
|
+
expect(issuesOf(STANDARD_HUMAN_DECLARATION)).toEqual([])
|
|
30
|
+
expect(
|
|
31
|
+
issuesOf({ in: pr, approve: pr, reject: instructionSchema() }),
|
|
32
|
+
).toEqual([])
|
|
33
|
+
// 未宣言({})も宣言の1つ(スキーマ未定義)
|
|
34
|
+
expect(issuesOf({ in: {}, approve: "T", reject: {} })).toEqual([])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("type の配列(nullable)は場所つきで弾く", () => {
|
|
38
|
+
expect(
|
|
39
|
+
issuesOf({
|
|
40
|
+
in: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: { summary: { type: ["string", "null"] } },
|
|
43
|
+
},
|
|
44
|
+
approve: "T",
|
|
45
|
+
reject: instructionSchema(),
|
|
46
|
+
}),
|
|
47
|
+
).toEqual([
|
|
48
|
+
"in.properties.summary.type: 配列(nullable)は使えません。任意の値は required から外してください",
|
|
49
|
+
])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("知らない type・形の崩れた properties / required / items を弾く", () => {
|
|
53
|
+
expect(
|
|
54
|
+
issuesOf({
|
|
55
|
+
in: { type: "strin" },
|
|
56
|
+
approve: { type: "object", properties: [] },
|
|
57
|
+
reject: {
|
|
58
|
+
type: "object",
|
|
59
|
+
required: [1],
|
|
60
|
+
properties: { list: { type: "array", items: "x" } },
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
).toEqual([
|
|
64
|
+
"in.type: type は object / array / string / number / integer / boolean のどれかにしてください(strin)",
|
|
65
|
+
"approve.properties: properties はオブジェクトで書いてください",
|
|
66
|
+
"reject.properties.list.items: items はスキーマ(オブジェクト)で書いてください",
|
|
67
|
+
"reject.required: required は文字列の配列で書いてください",
|
|
68
|
+
])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("type の無い構造(properties / items だけ)を弾く", () => {
|
|
72
|
+
expect(
|
|
73
|
+
issuesOf({
|
|
74
|
+
in: { properties: { a: { type: "string" } } },
|
|
75
|
+
approve: "T",
|
|
76
|
+
reject: instructionSchema(),
|
|
77
|
+
}),
|
|
78
|
+
).toEqual([
|
|
79
|
+
"in.type: properties / items を書くときは type も書いてください",
|
|
80
|
+
])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('reject は上流のコピー("T")にできない', () => {
|
|
84
|
+
expect(issuesOf({ in: "T", approve: "T", reject: "T" })).not.toEqual([])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("humanManifestSchema(nodes/human/<dir>/config.json)", () => {
|
|
89
|
+
it("省略した口は上流のコピー(in)・in と同型(approve)・標準の差し戻し(reject)になる", () => {
|
|
90
|
+
const config = humanManifestSchema.parse({})
|
|
91
|
+
expect(humanDeclarationOfManifest(config)).toEqual(
|
|
92
|
+
STANDARD_HUMAN_DECLARATION,
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("書いた口の型をそのまま宣言にする", () => {
|
|
97
|
+
const config = humanManifestSchema.parse({
|
|
98
|
+
inputs: { in: { schema: pr } },
|
|
99
|
+
outputs: { approve: { type: "array", items: pr }, reject: pr },
|
|
100
|
+
})
|
|
101
|
+
expect(humanDeclarationOfManifest(config)).toEqual({
|
|
102
|
+
in: pr,
|
|
103
|
+
approve: { type: "array", items: pr },
|
|
104
|
+
reject: pr,
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("in / approve / reject 以外の口・知らないキーは弾く", () => {
|
|
109
|
+
expect(
|
|
110
|
+
humanManifestSchema.safeParse({ inputs: { instruction: {} } }).success,
|
|
111
|
+
).toBe(false)
|
|
112
|
+
expect(
|
|
113
|
+
humanManifestSchema.safeParse({ outputs: { notion: {} } }).success,
|
|
114
|
+
).toBe(false)
|
|
115
|
+
expect(humanManifestSchema.safeParse({ name: "x" }).success).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("中の型も宣言の検査を通す", () => {
|
|
119
|
+
const parsed = humanManifestSchema.safeParse({
|
|
120
|
+
inputs: { in: { schema: { type: ["string", "null"] } } },
|
|
121
|
+
})
|
|
122
|
+
expect(parsed.success).toBe(false)
|
|
123
|
+
expect(
|
|
124
|
+
parsed.error?.issues.map((i) => `${i.path.join(".")}: ${i.message}`),
|
|
125
|
+
).toEqual([
|
|
126
|
+
"inputs.in.schema.type: 配列(nullable)は使えません。任意の値は required から外してください",
|
|
127
|
+
])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { type JsonSchema, jsonSchemaSchema } from "./node.js"
|
|
3
|
+
import {
|
|
4
|
+
COPY_UPSTREAM_SCHEMA,
|
|
5
|
+
type DeclaredPortSchema,
|
|
6
|
+
instructionSchema,
|
|
7
|
+
} from "./port-spec.js"
|
|
8
|
+
|
|
9
|
+
// ---- human ノードの宣言(in / approve / reject の型) ----
|
|
10
|
+
// 正は実装を持つ側(顧客 repo の nodes/human/<dir>/config.json。実装を指さないノードは標準の宣言)。
|
|
11
|
+
// human ノードは実装を選んだ時点の写し(node_humans.declaration)を持ち、伝播はそれを読む
|
|
12
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「宣言(ポート型)」)。
|
|
13
|
+
// "T"(COPY_UPSTREAM_SCHEMA)は「上流の out をそのまま受ける」(in)/「in と同型」(approve)。
|
|
14
|
+
// reject は具体型だけ(上流のコピーは意味を持たない)
|
|
15
|
+
|
|
16
|
+
export type HumanDeclaration = {
|
|
17
|
+
in: DeclaredPortSchema
|
|
18
|
+
approve: DeclaredPortSchema
|
|
19
|
+
reject: JsonSchema
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 宣言に書ける type。file は x-mawaru-type マーカー付きの object
|
|
23
|
+
const DECLARED_TYPES = [
|
|
24
|
+
"object",
|
|
25
|
+
"array",
|
|
26
|
+
"string",
|
|
27
|
+
"number",
|
|
28
|
+
"integer",
|
|
29
|
+
"boolean",
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|
33
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
34
|
+
|
|
35
|
+
// JSON Schema の形の崩れを場所つきで返す。接続の比較(schemaFit)が扱える形だけを通す:
|
|
36
|
+
// 顧客 repo の宣言は mawaru のレビューを通らないので、壊れた型がノードに写る前に止める
|
|
37
|
+
const schemaIssues = (
|
|
38
|
+
schema: Record<string, unknown>,
|
|
39
|
+
path: (string | number)[],
|
|
40
|
+
): { path: (string | number)[]; message: string }[] => {
|
|
41
|
+
const issues: { path: (string | number)[]; message: string }[] = []
|
|
42
|
+
const { type, properties, required, items } = schema
|
|
43
|
+
if (Array.isArray(type)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
path: [...path, "type"],
|
|
46
|
+
message:
|
|
47
|
+
"配列(nullable)は使えません。任意の値は required から外してください",
|
|
48
|
+
})
|
|
49
|
+
} else if (
|
|
50
|
+
type !== undefined &&
|
|
51
|
+
!DECLARED_TYPES.includes(type as (typeof DECLARED_TYPES)[number])
|
|
52
|
+
) {
|
|
53
|
+
issues.push({
|
|
54
|
+
path: [...path, "type"],
|
|
55
|
+
message: `type は ${DECLARED_TYPES.join(" / ")} のどれかにしてください(${String(type)})`,
|
|
56
|
+
})
|
|
57
|
+
} else if (
|
|
58
|
+
type === undefined &&
|
|
59
|
+
(properties !== undefined || items !== undefined)
|
|
60
|
+
) {
|
|
61
|
+
issues.push({
|
|
62
|
+
path: [...path, "type"],
|
|
63
|
+
message: "properties / items を書くときは type も書いてください",
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
if (properties !== undefined) {
|
|
67
|
+
if (!isPlainObject(properties)) {
|
|
68
|
+
issues.push({
|
|
69
|
+
path: [...path, "properties"],
|
|
70
|
+
message: "properties はオブジェクトで書いてください",
|
|
71
|
+
})
|
|
72
|
+
} else {
|
|
73
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
74
|
+
if (!isPlainObject(child)) {
|
|
75
|
+
issues.push({
|
|
76
|
+
path: [...path, "properties", key],
|
|
77
|
+
message: "スキーマはオブジェクトで書いてください",
|
|
78
|
+
})
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
issues.push(...schemaIssues(child, [...path, "properties", key]))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (
|
|
86
|
+
required !== undefined &&
|
|
87
|
+
!(Array.isArray(required) && required.every((r) => typeof r === "string"))
|
|
88
|
+
) {
|
|
89
|
+
issues.push({
|
|
90
|
+
path: [...path, "required"],
|
|
91
|
+
message: "required は文字列の配列で書いてください",
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
if (items !== undefined) {
|
|
95
|
+
if (!isPlainObject(items)) {
|
|
96
|
+
issues.push({
|
|
97
|
+
path: [...path, "items"],
|
|
98
|
+
message: "items はスキーマ(オブジェクト)で書いてください",
|
|
99
|
+
})
|
|
100
|
+
} else {
|
|
101
|
+
issues.push(...schemaIssues(items, [...path, "items"]))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return issues
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 宣言に書く1つの型(検査つき)
|
|
108
|
+
const checkedSchema = jsonSchemaSchema.superRefine((schema, ctx) => {
|
|
109
|
+
for (const issue of schemaIssues(schema, []))
|
|
110
|
+
ctx.addIssue({ code: "custom", ...issue })
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const declaredPortSchema = z.union([
|
|
114
|
+
z.literal(COPY_UPSTREAM_SCHEMA),
|
|
115
|
+
checkedSchema,
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
export const humanDeclarationSchema = z.object({
|
|
119
|
+
in: declaredPortSchema,
|
|
120
|
+
approve: declaredPortSchema,
|
|
121
|
+
reject: checkedSchema,
|
|
122
|
+
}) satisfies z.ZodType<HumanDeclaration>
|
|
123
|
+
|
|
124
|
+
// 標準ビュー(common/standard)の宣言:上流の型をそのまま受けて出し、差し戻しは標準の指示フォーム
|
|
125
|
+
export const STANDARD_HUMAN_DECLARATION: HumanDeclaration = {
|
|
126
|
+
in: COPY_UPSTREAM_SCHEMA,
|
|
127
|
+
approve: COPY_UPSTREAM_SCHEMA,
|
|
128
|
+
reject: instructionSchema(),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---- 顧客 repo の nodes/human/<dir>/config.json ----
|
|
132
|
+
// 形は AI の config.json に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
|
|
133
|
+
// 口は in / approve / reject 固定。省略した口は STANDARD_HUMAN_DECLARATION と同じ扱い
|
|
134
|
+
export const humanManifestSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
inputs: z
|
|
137
|
+
.object({ in: z.object({ schema: checkedSchema }).strict().optional() })
|
|
138
|
+
.strict()
|
|
139
|
+
.optional(),
|
|
140
|
+
outputs: z
|
|
141
|
+
.object({
|
|
142
|
+
approve: checkedSchema.optional(),
|
|
143
|
+
reject: checkedSchema.optional(),
|
|
144
|
+
})
|
|
145
|
+
.strict()
|
|
146
|
+
.optional(),
|
|
147
|
+
})
|
|
148
|
+
.strict()
|
|
149
|
+
|
|
150
|
+
export type HumanManifest = z.infer<typeof humanManifestSchema>
|
|
151
|
+
|
|
152
|
+
export const humanDeclarationOfManifest = (
|
|
153
|
+
config: HumanManifest,
|
|
154
|
+
): HumanDeclaration => ({
|
|
155
|
+
in: config.inputs?.in?.schema ?? COPY_UPSTREAM_SCHEMA,
|
|
156
|
+
approve: config.outputs?.approve ?? COPY_UPSTREAM_SCHEMA,
|
|
157
|
+
reject: config.outputs?.reject ?? instructionSchema(),
|
|
158
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
+
import type { HumanDeclaration } from "./human-manifest.js"
|
|
2
3
|
import type { GraphPort, JsonSchema } from "./node.js"
|
|
3
4
|
import {
|
|
4
5
|
arrayOf,
|
|
@@ -153,6 +154,15 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
|
|
|
153
154
|
})
|
|
154
155
|
})
|
|
155
156
|
|
|
157
|
+
// human ノードに実装の宣言の写し(node_humans.declaration)を持たせる
|
|
158
|
+
const withViewDeclaration = <N extends { kind: string }>(
|
|
159
|
+
nodes: N[],
|
|
160
|
+
declaration: HumanDeclaration,
|
|
161
|
+
) =>
|
|
162
|
+
nodes.map((node) =>
|
|
163
|
+
node.kind === "human" ? { ...node, human: { declaration } } : node,
|
|
164
|
+
)
|
|
165
|
+
|
|
156
166
|
describe("型伝播(導出ポートの解決)", () => {
|
|
157
167
|
it.each([
|
|
158
168
|
"notion_task",
|
|
@@ -173,13 +183,14 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
173
183
|
extra,
|
|
174
184
|
],
|
|
175
185
|
}
|
|
176
|
-
const resolved = resolvePortSchemas(
|
|
177
|
-
|
|
186
|
+
const resolved = resolvePortSchemas(
|
|
187
|
+
withViewDeclaration([human], {
|
|
178
188
|
in: { type: "string" },
|
|
179
189
|
approve: { type: "string" },
|
|
180
190
|
reject: { type: "string" },
|
|
181
191
|
}),
|
|
182
|
-
|
|
192
|
+
[],
|
|
193
|
+
)
|
|
183
194
|
expect(resolved.get(extra.id)).toEqual(extra.schema)
|
|
184
195
|
})
|
|
185
196
|
|
|
@@ -191,7 +202,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
191
202
|
}
|
|
192
203
|
|
|
193
204
|
// human の in / approve / reject はビューの宣言をそのまま転写する(上流の kind は見ない)。
|
|
194
|
-
// sdk
|
|
205
|
+
// 宣言はノードの写し(declaration)を読む(sdk はカタログを知らない)
|
|
195
206
|
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
196
207
|
it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject はビューの宣言型", () => {
|
|
197
208
|
const aiOut = port("out", "main", declaredOut)
|
|
@@ -209,15 +220,12 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
209
220
|
ports: [humanIn, approve, reject],
|
|
210
221
|
}
|
|
211
222
|
const resolved = resolvePortSchemas(
|
|
212
|
-
[ai, human],
|
|
223
|
+
withViewDeclaration([ai, human], {
|
|
224
|
+
in: "T",
|
|
225
|
+
approve: "T",
|
|
226
|
+
reject: instructionSchema(),
|
|
227
|
+
}),
|
|
213
228
|
[{ from_port_id: aiOut.id, to_port_id: humanIn.id }],
|
|
214
|
-
{
|
|
215
|
-
viewPortSchemasOf: () => ({
|
|
216
|
-
in: "T",
|
|
217
|
-
approve: "T",
|
|
218
|
-
reject: instructionSchema(),
|
|
219
|
-
}),
|
|
220
|
-
},
|
|
221
229
|
)
|
|
222
230
|
// 上流が AI でも中身を取り出さない:AI の出力形式がそのまま下流へ流れる
|
|
223
231
|
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
@@ -225,7 +233,7 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
225
233
|
expect(resolved.get(reject.id)).toEqual(instructionSchema())
|
|
226
234
|
})
|
|
227
235
|
|
|
228
|
-
describe("
|
|
236
|
+
describe("実装の宣言を human のポートへ転写する", () => {
|
|
229
237
|
const viewIn = {
|
|
230
238
|
type: "object",
|
|
231
239
|
properties: {
|
|
@@ -274,15 +282,12 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
274
282
|
declaredOut,
|
|
275
283
|
)
|
|
276
284
|
const resolved = resolvePortSchemas(
|
|
277
|
-
[upstream, human],
|
|
285
|
+
withViewDeclaration([upstream, human], {
|
|
286
|
+
in: viewIn,
|
|
287
|
+
approve: viewOut,
|
|
288
|
+
reject: viewReject,
|
|
289
|
+
}),
|
|
278
290
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
279
|
-
{
|
|
280
|
-
viewPortSchemasOf: () => ({
|
|
281
|
-
in: viewIn,
|
|
282
|
-
approve: viewOut,
|
|
283
|
-
reject: viewReject,
|
|
284
|
-
}),
|
|
285
|
-
},
|
|
286
291
|
)
|
|
287
292
|
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
288
293
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
@@ -293,15 +298,12 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
293
298
|
it("上流の out は書き換えない(逆伝播しない)", () => {
|
|
294
299
|
const { upstream, human, out, humanIn } = build("ai", declaredOut)
|
|
295
300
|
const resolved = resolvePortSchemas(
|
|
296
|
-
[upstream, human],
|
|
301
|
+
withViewDeclaration([upstream, human], {
|
|
302
|
+
in: viewIn,
|
|
303
|
+
approve: viewOut,
|
|
304
|
+
reject: viewReject,
|
|
305
|
+
}),
|
|
297
306
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
298
|
-
{
|
|
299
|
-
viewPortSchemasOf: () => ({
|
|
300
|
-
in: viewIn,
|
|
301
|
-
approve: viewOut,
|
|
302
|
-
reject: viewReject,
|
|
303
|
-
}),
|
|
304
|
-
},
|
|
305
307
|
)
|
|
306
308
|
expect(resolved.get(out.id)).toEqual(declaredOut)
|
|
307
309
|
})
|
|
@@ -312,15 +314,12 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
312
314
|
declaredOut,
|
|
313
315
|
)
|
|
314
316
|
const resolved = resolvePortSchemas(
|
|
315
|
-
[upstream, human],
|
|
317
|
+
withViewDeclaration([upstream, human], {
|
|
318
|
+
in: "T",
|
|
319
|
+
approve: "T",
|
|
320
|
+
reject: viewReject,
|
|
321
|
+
}),
|
|
316
322
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
317
|
-
{
|
|
318
|
-
viewPortSchemasOf: () => ({
|
|
319
|
-
in: "T",
|
|
320
|
-
approve: "T",
|
|
321
|
-
reject: viewReject,
|
|
322
|
-
}),
|
|
323
|
-
},
|
|
324
323
|
)
|
|
325
324
|
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
326
325
|
expect(resolved.get(approve.id)).toEqual(declaredOut)
|
|
@@ -335,13 +334,14 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
335
334
|
kind: "human",
|
|
336
335
|
ports: [humanIn, approve, reject],
|
|
337
336
|
}
|
|
338
|
-
const resolved = resolvePortSchemas(
|
|
339
|
-
|
|
337
|
+
const resolved = resolvePortSchemas(
|
|
338
|
+
withViewDeclaration([human], {
|
|
340
339
|
in: viewIn,
|
|
341
340
|
approve: viewOut,
|
|
342
341
|
reject: viewReject,
|
|
343
342
|
}),
|
|
344
|
-
|
|
343
|
+
[],
|
|
344
|
+
)
|
|
345
345
|
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
346
346
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
347
347
|
expect(resolved.get(reject.id)).toEqual(viewReject)
|
package/_schemas/port-spec.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
+
import type { HumanDeclaration } from "./human-manifest.js"
|
|
2
3
|
import type { GraphPort, JsonSchema, PortIo } from "./node.js"
|
|
3
4
|
|
|
4
5
|
// ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
|
|
5
|
-
// 型の決まり方は2分類:宣言(program / ai の in・out。human
|
|
6
|
+
// 型の決まり方は2分類:宣言(program / ai の in・out。human は実装(nodes/human/)の宣言を転写)/
|
|
6
7
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
7
8
|
// mawaru がコードで型を固定するポートは無い
|
|
8
9
|
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
@@ -265,7 +266,7 @@ export const kindPortsViolation = (
|
|
|
265
266
|
// ---- 型伝播(導出ポートの解決) ----
|
|
266
267
|
// 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
|
|
267
268
|
// wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
|
|
268
|
-
// human
|
|
269
|
+
// human は実装(nodes/human/)の宣言を転写("T" は素通しと同じ挙動。reject もビューの宣言)。
|
|
269
270
|
// 宣言 out(program / ai)は触らない。
|
|
270
271
|
// program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
271
272
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
@@ -328,6 +329,9 @@ type PropagationNode = {
|
|
|
328
329
|
id: string
|
|
329
330
|
kind: string
|
|
330
331
|
ports: GraphPort[]
|
|
332
|
+
// human のときだけ意味を持つ:実装(nodes/human/<dir>/)の宣言の写し(node_humans.declaration)。
|
|
333
|
+
// 無ければ in・approve は上流のコピー、reject は触らない
|
|
334
|
+
human?: { declaration?: HumanDeclaration } | null
|
|
331
335
|
}
|
|
332
336
|
type PortLink = { from_port_id: string; to_port_id: string }
|
|
333
337
|
|
|
@@ -338,23 +342,6 @@ export const COPY_UPSTREAM_SCHEMA = "T"
|
|
|
338
342
|
// ポートの型宣言:具体型か、上流からのコピー指示か
|
|
339
343
|
export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA
|
|
340
344
|
|
|
341
|
-
// 伝播のオプション。sdk は承認ビューのカタログを知らない(非公開。企業向けビューの
|
|
342
|
-
// キーが npm 公開物に載るのを避ける)ので、呼び出し元が解決して渡す
|
|
343
|
-
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
344
|
-
export type PropagationOptions<N> = {
|
|
345
|
-
// human の in / approve / reject に転写する型(承認ビューの宣言)。null / undefined
|
|
346
|
-
// ならその node は対象外(human 以外)。上流ノードの kind は一切見ない。
|
|
347
|
-
// reject は具体型のみ(上流からのコピー指示は意味を持たない)
|
|
348
|
-
viewPortSchemasOf?: (node: N) =>
|
|
349
|
-
| {
|
|
350
|
-
in: DeclaredPortSchema
|
|
351
|
-
approve: DeclaredPortSchema
|
|
352
|
-
reject: JsonSchema
|
|
353
|
-
}
|
|
354
|
-
| null
|
|
355
|
-
| undefined
|
|
356
|
-
}
|
|
357
|
-
|
|
358
345
|
// end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
|
|
359
346
|
// resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
|
|
360
347
|
// start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
|
|
@@ -380,7 +367,6 @@ const isDerivedPort = (kind: string, port: Pick<GraphPort, "io" | "key">) =>
|
|
|
380
367
|
export const resolvePortSchemas = <N extends PropagationNode>(
|
|
381
368
|
nodes: N[],
|
|
382
369
|
connections: PortLink[],
|
|
383
|
-
options?: PropagationOptions<N>,
|
|
384
370
|
): Map<string, JsonSchema> => {
|
|
385
371
|
const resolved = new Map<string, JsonSchema>()
|
|
386
372
|
const kindByPortId = new Map<string, string>()
|
|
@@ -504,13 +490,14 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
504
490
|
const inSchema = mergeInboundSchemas(
|
|
505
491
|
sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA),
|
|
506
492
|
)
|
|
507
|
-
//
|
|
493
|
+
// 実装(nodes/human/<dir>/)の宣言を human のポートへそのまま転写する(上流ノードの kind は見ない)。
|
|
508
494
|
// "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
|
|
509
495
|
// のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
|
|
510
496
|
// 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
|
|
511
497
|
// 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
|
|
512
498
|
// ズレは接続の型エラーで見せてユーザーの [修正する] で直す
|
|
513
|
-
const declared =
|
|
499
|
+
const declared =
|
|
500
|
+
node.kind === "human" ? node.human?.declaration : undefined
|
|
514
501
|
const effectiveIn =
|
|
515
502
|
declared && declared.in !== COPY_UPSTREAM_SCHEMA
|
|
516
503
|
? declared.in
|
|
@@ -588,9 +575,8 @@ export const connectionAccepted = (
|
|
|
588
575
|
export const propagateDerivedPorts = <N extends PropagationNode>(
|
|
589
576
|
nodes: N[],
|
|
590
577
|
connections: PortLink[],
|
|
591
|
-
options?: PropagationOptions<N>,
|
|
592
578
|
): N[] => {
|
|
593
|
-
const resolved = resolvePortSchemas(nodes, connections
|
|
579
|
+
const resolved = resolvePortSchemas(nodes, connections)
|
|
594
580
|
const flexiblePorts = resolveFlexiblePorts(nodes, connections)
|
|
595
581
|
return nodes.map((node) => {
|
|
596
582
|
let touched = false
|
package/dist/_cli/login.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { createServer } from "node:http";
|
|
4
3
|
import { saveCredentials } from "./credentials.js";
|
|
4
|
+
import { openBrowser } from "./openBrowser.js";
|
|
5
5
|
// `gh auth login` と同じループバック方式(RFC 8252)。
|
|
6
6
|
// CLI が 127.0.0.1 の一時ポートで待ち受け、ブラウザで mawaru の /cli-auth を開く。
|
|
7
7
|
// 承認するとブラウザがループバック URL へ**遷移**してトークンを渡す
|
|
@@ -11,20 +11,6 @@ const page = (title, message) => `<!doctype html><meta charset="utf-8"><title>${
|
|
|
11
11
|
`<body style="font-family:system-ui;display:grid;place-items:center;height:100vh;margin:0">` +
|
|
12
12
|
`<div style="text-align:center"><h1 style="font-size:1.25rem">${title}</h1>` +
|
|
13
13
|
`<p style="color:#666">${message}</p></div></body>`;
|
|
14
|
-
// ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
|
|
15
|
-
const openBrowser = (url) => {
|
|
16
|
-
const [command, args] = process.platform === "darwin"
|
|
17
|
-
? ["open", [url]]
|
|
18
|
-
: process.platform === "win32"
|
|
19
|
-
? ["cmd", ["/c", "start", "", url]]
|
|
20
|
-
: ["xdg-open", [url]];
|
|
21
|
-
try {
|
|
22
|
-
spawn(command, args, { stdio: "ignore", detached: true }).unref();
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// 何もしない(URL は既に印字済み)
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
14
|
export const runLogin = async (timeoutMs = 5 * 60 * 1000) => {
|
|
29
15
|
// 別のローカルプロセスが投げ込んだ応答を受け付けないための照合値
|
|
30
16
|
const state = randomUUID();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const openBrowser: (url: string) => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
// ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
|
|
3
|
+
export const openBrowser = (url) => {
|
|
4
|
+
const [command, args] = process.platform === "darwin"
|
|
5
|
+
? ["open", [url]]
|
|
6
|
+
: process.platform === "win32"
|
|
7
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
8
|
+
: ["xdg-open", [url]];
|
|
9
|
+
try {
|
|
10
|
+
spawn(command, args, { stdio: "ignore", detached: true }).unref();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// 何もしない(URL は既に印字済み)
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Credentials } from "./credentials.js";
|
|
2
|
-
export type MawaruApi = (method: "GET" | "POST", path: string, body?: unknown) => Promise<{
|
|
2
|
+
export type MawaruApi = (method: "GET" | "POST" | "PUT", path: string, body?: unknown) => Promise<{
|
|
3
3
|
status: number;
|
|
4
4
|
body: unknown;
|
|
5
5
|
}>;
|