@mawaru/sdk 0.30.0 → 0.32.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.
@@ -548,6 +548,14 @@ const outByKey = (node: TestNode, key: string): GraphPort => {
548
548
  }
549
549
 
550
550
  describe("saveGraphBodySchema(kind 別ポート構成)", () => {
551
+ it("human の追加出口を保存できる", () => {
552
+ const human = humanNode()
553
+ human.ports.push(port("out", "notion_task"))
554
+ expect(() =>
555
+ saveGraphBodySchema.parse({ nodes: [human], connections: [] }),
556
+ ).not.toThrow()
557
+ })
558
+
551
559
  it("human の担当なし(human 設定の省略)を拒否する", () => {
552
560
  const { human: _human, ...noAssign } = humanNode()
553
561
  expect(() =>
@@ -90,7 +90,26 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
90
90
  ).toBeNull()
91
91
  })
92
92
 
93
- it("human は in / approve / reject 固定", () => {
93
+ it("human は追加の出口を持てるが基本ポートは必須", () => {
94
+ const ports = [
95
+ port("in", "in"),
96
+ port("out", "approve"),
97
+ port("out", "reject"),
98
+ port("out", "notion_task"),
99
+ ]
100
+ expect(kindPortsViolation("human", ports)).toBeNull()
101
+ expect(
102
+ kindPortsViolation(
103
+ "human",
104
+ ports.filter((p) => p.key !== "reject"),
105
+ ),
106
+ ).toMatch(/human/)
107
+ expect(
108
+ kindPortsViolation("human", [...ports, port("in", "extra")]),
109
+ ).toMatch(/human/)
110
+ })
111
+
112
+ it("human は in / approve / reject を持つ", () => {
94
113
  expect(
95
114
  kindPortsViolation("human", [
96
115
  port("in", "in"),
@@ -132,6 +151,35 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
132
151
  })
133
152
 
134
153
  describe("型伝播(導出ポートの解決)", () => {
154
+ it.each([
155
+ "notion_task",
156
+ "main",
157
+ ])("Humanの追加出口 %s の宣言型は承認ビューの型で上書きしない", (key) => {
158
+ const extra = port("out", key, {
159
+ type: "object",
160
+ properties: { title: { type: "string" } },
161
+ required: ["title"],
162
+ })
163
+ const human = {
164
+ id: uuid(),
165
+ kind: "human",
166
+ ports: [
167
+ port("in", "in"),
168
+ port("out", "approve"),
169
+ port("out", "reject"),
170
+ extra,
171
+ ],
172
+ }
173
+ const resolved = resolvePortSchemas([human], [], {
174
+ viewPortSchemasOf: () => ({
175
+ in: { type: "string" },
176
+ approve: { type: "string" },
177
+ reject: { type: "string" },
178
+ }),
179
+ })
180
+ expect(resolved.get(extra.id)).toEqual(extra.schema)
181
+ })
182
+
135
183
  // 伝播テスト用の任意の宣言型(中身の形に意味はない)
136
184
  const declaredOut = {
137
185
  type: "object",
@@ -139,11 +139,10 @@ export const dataFormatOf = (schema: JsonSchema): DataFormat | null => {
139
139
  }
140
140
 
141
141
  // ---- kind 別ポート構成 ----
142
- // human / wait は構成(数・key・io)が固定。program / ai は in 1つ+out 1つ以上
142
+ // wait は構成が固定。human は基本ポートを保ち追加 out を持てる。program / ai は out 自由
143
143
  // (key 自由。複数 out=分岐。AIノードの複数出口(分岐).md)
144
144
 
145
145
  const FIXED_PORT_KEYS: Record<string, { in: string[]; out: string[] }> = {
146
- human: { in: ["in"], out: ["approve", "reject"] },
147
146
  wait: { in: ["in"], out: ["main"] },
148
147
  // start は in 1つ(run 入力契約)+ out main 1つの恒等パススルー
149
148
  start: { in: ["in"], out: ["main"] },
@@ -199,6 +198,19 @@ export const kindPortsViolation = (
199
198
  }
200
199
  return null
201
200
  }
201
+ if (kind === "human") {
202
+ const inKeys = ports.filter((p) => p.io === "in").map((p) => p.key)
203
+ const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key)
204
+ if (
205
+ inKeys.length !== 1 ||
206
+ inKeys[0] !== "in" ||
207
+ !outKeys.includes("approve") ||
208
+ !outKeys.includes("reject")
209
+ ) {
210
+ return "human のポート構成は in(in) と out(approve / reject) が必須です(out は追加できます)"
211
+ }
212
+ return null
213
+ }
202
214
  const fixed = FIXED_PORT_KEYS[kind]
203
215
  if (!fixed) {
204
216
  // program / ai(自由 out):in の構成だけ固定
@@ -334,7 +346,7 @@ const MIRROR_KINDS = new Set(["program", "ai"])
334
346
  const isDerivedPort = (kind: string, port: Pick<GraphPort, "io" | "key">) =>
335
347
  DERIVED_KINDS.has(kind) &&
336
348
  (port.io === "in" ||
337
- port.key === "main" ||
349
+ (kind !== "human" && port.key === "main") ||
338
350
  port.key === "approve" ||
339
351
  port.key === "reject")
340
352
 
@@ -58,11 +58,11 @@ export const accessTokenFrom = async (credentials) => {
58
58
  body: JSON.stringify({ refresh_token: credentials.refresh_token }),
59
59
  });
60
60
  if (!res.ok) {
61
- throw new Error(`セッションの更新に失敗しました(HTTP ${res.status})。\`mawaru login\` でログインし直してください。`);
61
+ throw new Error(`セッションの更新に失敗しました(HTTP ${res.status})。\`npx mawaru login\` でログインし直してください。`);
62
62
  }
63
63
  const body = (await res.json());
64
64
  if (!body.access_token || !body.refresh_token) {
65
- throw new Error("セッションの更新に失敗しました。`mawaru login` でログインし直してください。");
65
+ throw new Error("セッションの更新に失敗しました。`npx mawaru login` でログインし直してください。");
66
66
  }
67
67
  saveCredentials({
68
68
  ...credentials,
@@ -75,7 +75,7 @@ export const accessTokenFrom = async (credentials) => {
75
75
  export const requireCredentials = () => {
76
76
  const credentials = loadCredentials();
77
77
  if (!credentials) {
78
- throw new Error("ログインしていません。`mawaru login` を実行してください。");
78
+ throw new Error("ログインしていません。`npx mawaru login` を実行してください。");
79
79
  }
80
80
  return credentials;
81
81
  };
@@ -17,7 +17,7 @@ const tilde = (path) => {
17
17
  // 移動もしない。手がかりとして対象 repo 名だけ見せ、現在地と食い違うときに警告する
18
18
  export const runSessionResume = async (stepUrl) => {
19
19
  if (!stepUrl) {
20
- console.error("使い方: mawaru session resume <step URL>\n" +
20
+ console.error("使い方: npx mawaru session resume <step URL>(実行 repo で実行)\n" +
21
21
  "run 詳細の AI ノードにあるコピーボタンで URL を取得できます。");
22
22
  return 1;
23
23
  }
@@ -118,10 +118,9 @@ export const dataFormatOf = (schema) => {
118
118
  return { format: "list", columns };
119
119
  };
120
120
  // ---- kind 別ポート構成 ----
121
- // human / wait は構成(数・key・io)が固定。program / ai は in 1つ+out 1つ以上
121
+ // wait は構成が固定。human は基本ポートを保ち追加 out を持てる。program / ai は out 自由
122
122
  // (key 自由。複数 out=分岐。AIノードの複数出口(分岐).md)
123
123
  const FIXED_PORT_KEYS = {
124
- human: { in: ["in"], out: ["approve", "reject"] },
125
124
  wait: { in: ["in"], out: ["main"] },
126
125
  // start は in 1つ(run 入力契約)+ out main 1つの恒等パススルー
127
126
  start: { in: ["in"], out: ["main"] },
@@ -166,6 +165,17 @@ export const kindPortsViolation = (kind, ports) => {
166
165
  }
167
166
  return null;
168
167
  }
168
+ if (kind === "human") {
169
+ const inKeys = ports.filter((p) => p.io === "in").map((p) => p.key);
170
+ const outKeys = ports.filter((p) => p.io === "out").map((p) => p.key);
171
+ if (inKeys.length !== 1 ||
172
+ inKeys[0] !== "in" ||
173
+ !outKeys.includes("approve") ||
174
+ !outKeys.includes("reject")) {
175
+ return "human のポート構成は in(in) と out(approve / reject) が必須です(out は追加できます)";
176
+ }
177
+ return null;
178
+ }
169
179
  const fixed = FIXED_PORT_KEYS[kind];
170
180
  if (!fixed) {
171
181
  // program / ai(自由 out):in の構成だけ固定
@@ -258,7 +268,7 @@ const DERIVED_KINDS = new Set(["wait", "human", "end", "start"]);
258
268
  const MIRROR_KINDS = new Set(["program", "ai"]);
259
269
  const isDerivedPort = (kind, port) => DERIVED_KINDS.has(kind) &&
260
270
  (port.io === "in" ||
261
- port.key === "main" ||
271
+ (kind !== "human" && port.key === "main") ||
262
272
  port.key === "approve" ||
263
273
  port.key === "reject");
264
274
  // port_id → 解決済みスキーマ。導出は接続を遡って固定点まで反復し、
package/dist/cli.js CHANGED
@@ -81,7 +81,7 @@ switch (command) {
81
81
  case "session": {
82
82
  const [subcommand, ...args] = rest;
83
83
  if (subcommand !== "resume") {
84
- fail("使い方: mawaru session resume <step URL>");
84
+ fail("使い方: npx mawaru session resume <step URL>");
85
85
  }
86
86
  try {
87
87
  const code = await runSessionResume(args.find((a) => !a.startsWith("-")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mawaru/sdk",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,7 +59,7 @@ secret)でも、**足りない値を推測・生成・空文字で代用しな
59
59
 
60
60
  疎通確認のあと `GET {MAWARU_API_URL}/tenants/{MAWARU_TENANT_ID}/openapi.json`
61
61
  (ヘッダ `x-api-key: $MAWARU_API_KEY`)を取得すると、機械 API 面
62
- (loops の一覧・作成・詳細・graph PUT、runs/start、runs/{runId}/cancel、members、api-keys、steps の end / decision)の
62
+ (loops の一覧・作成・詳細・graph PUT、runs/start、runs/{runId}/cancel、runs/{runId}/complete、members、api-keys、steps の end / decision)の
63
63
  パス・メソッド・リクエストボディ(Zod 由来の JSON Schema)が載っている。ここが常に正。
64
64
  段階導入中で未掲載のエンドポイントもある(その場合だけ `api/index.ts` 相当を辿る)。
65
65