@mawaru/sdk 0.28.0 → 0.30.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.
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { AI_MODELS, aiManifestSchema } from "./ai-manifest.js"
2
+ import {
3
+ AI_MODEL_GROUPS,
4
+ AI_MODELS,
5
+ agentOfModel,
6
+ aiManifestSchema,
7
+ } from "./ai-manifest.js"
3
8
  import { instructionSchema } from "./port-spec.js"
4
9
 
5
10
  const textOutput = { type: "string" }
@@ -16,6 +21,21 @@ const valid = {
16
21
  }
17
22
 
18
23
  describe("aiManifestSchema", () => {
24
+ it("認証方式を保持し、不明な方式は拒否する", () => {
25
+ for (const auth_mode of ["subscription", "api"]) {
26
+ expect(
27
+ aiManifestSchema.parse({ ...valid, model: "gpt-6-astra", auth_mode })
28
+ .auth_mode,
29
+ ).toBe(auth_mode)
30
+ }
31
+ expect(() =>
32
+ aiManifestSchema.parse({ ...valid, auth_mode: "auto" }),
33
+ ).toThrow()
34
+ // 旧定義を編集できるよう読み込みは許可する。実行には明示選択が必要。
35
+ expect(
36
+ aiManifestSchema.parse({ ...valid, model: "gpt-6-astra" }).auth_mode,
37
+ ).toBeUndefined()
38
+ })
19
39
  it("フル指定の manifest を受け付ける", () => {
20
40
  const manifest = aiManifestSchema.parse(valid)
21
41
  expect(manifest.name).toBe("返信下書き")
@@ -56,6 +76,25 @@ describe("aiManifestSchema", () => {
56
76
  ).toThrow()
57
77
  })
58
78
 
79
+ it("Codex のモデルも選べる(実行主体は model の接頭辞で決まる)", () => {
80
+ expect(
81
+ aiManifestSchema.parse({ ...valid, model: "gpt-6-astra" }).model,
82
+ ).toBe("gpt-6-astra")
83
+ for (const model of AI_MODEL_GROUPS.claude.models) {
84
+ expect(agentOfModel(model)).toBe("claude")
85
+ }
86
+ for (const model of AI_MODEL_GROUPS.codex.models) {
87
+ expect(agentOfModel(model)).toBe("codex")
88
+ }
89
+ // 各グループの既定はそのグループの選択肢に含まれる
90
+ expect(AI_MODEL_GROUPS.claude.models).toContain(
91
+ AI_MODEL_GROUPS.claude.default,
92
+ )
93
+ expect(AI_MODEL_GROUPS.codex.models).toContain(
94
+ AI_MODEL_GROUPS.codex.default,
95
+ )
96
+ })
97
+
59
98
  it("inputs は key 自由・1件以上(in / instruction の固定構成は廃止)", () => {
60
99
  // 0件は拒否
61
100
  expect(() => aiManifestSchema.parse({ ...valid, inputs: {} })).toThrow()
@@ -1,20 +1,54 @@
1
1
  import { z } from "zod"
2
2
  import { jsonSchemaSchema, PORT_KEY_PATTERN } from "./node.js"
3
3
 
4
- // AI ノードのモデル選択肢(既定は Opus:下書き品質が価値の中心。費用はユーザー持ち)。
5
- // エイリアス("opus" 等)は採らずバージョンを固定する:実行のたびに黙って別モデルへ
6
- // 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する
4
+ // AI ノードの実行主体(エージェント CLI)は Claude Code と Codex の2つ。Claude Code は Claude の
5
+ // モデルだけ、Codex は OpenAI のモデルだけを走らせるので、モデル ID と実行主体は 1:1。
6
+ // config.json に agent のようなフィールドは持たず、model の接頭辞(claude-* / gpt-*)で決まる
7
+ // (runner の _agents/index.ts と同じ規則。AI実行エージェントの抽象化(Claude CodeとCodex).md)。
8
+ //
9
+ // モデルはエイリアス("opus" 等)を採らずバージョンを固定する:実行のたびに黙って別モデルへ
10
+ // 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する。
11
+ // 既定は Opus:下書き品質が価値の中心。費用はユーザー持ち
12
+ export const AI_MODEL_GROUPS = {
13
+ claude: {
14
+ name: "Claude Code",
15
+ models: [
16
+ "claude-fable-5-1",
17
+ "claude-fable-5",
18
+ "claude-opus-5",
19
+ "claude-opus-4-8",
20
+ "claude-sonnet-5",
21
+ "claude-haiku-4-5",
22
+ ],
23
+ default: "claude-opus-5",
24
+ },
25
+ codex: {
26
+ name: "Codex",
27
+ models: [
28
+ "gpt-6-astra",
29
+ "gpt-5.6-sol",
30
+ "gpt-5.6-terra",
31
+ "gpt-5.6-luna",
32
+ "gpt-5.5",
33
+ ],
34
+ default: "gpt-6-astra",
35
+ },
36
+ } as const
37
+ export const AI_AGENT_IDS = ["claude", "codex"] as const
38
+ export const aiAgentIdSchema = z.enum(AI_AGENT_IDS)
39
+ export type AiAgentId = z.infer<typeof aiAgentIdSchema>
40
+
7
41
  export const AI_MODELS = [
8
- "claude-fable-5-1",
9
- "claude-fable-5",
10
- "claude-opus-5",
11
- "claude-opus-4-8",
12
- "claude-sonnet-5",
13
- "claude-haiku-4-5",
42
+ ...AI_MODEL_GROUPS.claude.models,
43
+ ...AI_MODEL_GROUPS.codex.models,
14
44
  ] as const
15
45
  export const aiModelSchema = z.enum(AI_MODELS)
16
46
  export type AiModel = z.infer<typeof aiModelSchema>
17
- export const DEFAULT_AI_MODEL = "claude-opus-5" satisfies AiModel
47
+ export const DEFAULT_AI_MODEL = AI_MODEL_GROUPS.claude.default satisfies AiModel
48
+
49
+ // モデル ID → 実行主体
50
+ export const agentOfModel = (model: AiModel): AiAgentId =>
51
+ model.startsWith("claude-") ? "claude" : "codex"
18
52
 
19
53
  // 入力口の宣言:スキーマ+実行属性。resume: true の口から入力が届いた実行は
20
54
  // 同じノードの直近セッションを復元して再開する(backend が ports.resume へ同期して
@@ -45,6 +79,8 @@ export const aiManifestSchema = z
45
79
  // repo に置く時点で完成品なので必須(DB 時代の「書きかけ空文字」は廃止)
46
80
  prompt: z.string().min(1),
47
81
  model: aiModelSchema.default(DEFAULT_AI_MODEL),
82
+ // 旧定義も編集のため読める。Codex の実行・保存時には明示選択を必須にする
83
+ auth_mode: z.enum(["subscription", "api"]).optional(),
48
84
  // 同 repo のトップレベル skills/ 配下のディレクトリ名
49
85
  skills: z
50
86
  .array(
@@ -13,10 +13,16 @@ export declare const workspaceRepoName: (cwds: string[]) => string | null;
13
13
  export declare const findProjectDir: (cwd: string) => string | null;
14
14
  export type TranscriptRef = {
15
15
  url: string;
16
+ agent: "claude" | "codex";
16
17
  session_id: string;
18
+ local_path: string;
17
19
  redacted?: number;
18
20
  };
19
21
  export declare const fetchTranscriptRef: (credentials: Credentials, ref: StepRef) => Promise<TranscriptRef>;
20
22
  export declare const downloadTranscript: (url: string) => Promise<string>;
21
- export declare const placeTranscript: (cwd: string, sessionId: string, transcript: string) => string;
22
- export declare const spawnClaudeResume: (sessionId: string) => Promise<number>;
23
+ export declare const placeTranscript: (cwd: string, ref: Pick<TranscriptRef, "agent" | "session_id" | "local_path">, transcript: string) => string;
24
+ export declare const resumeCommand: (agent: TranscriptRef["agent"], sessionId: string) => {
25
+ bin: string;
26
+ args: string[];
27
+ };
28
+ export declare const spawnResume: (agent: TranscriptRef["agent"], sessionId: string) => Promise<number>;
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { join } from "node:path";
4
+ import { dirname, join } from "node:path";
5
5
  import { accessTokenFrom } from "./credentials.js";
6
6
  // UI がコピーさせる web の URL から tenant / run / step を取り出す。
7
7
  // 3つを1つの貼れる文字列で運ぶために URL の形にしている
@@ -28,6 +28,7 @@ export const parseStepUrl = (raw) => {
28
28
  return { tenantId, runId, stepId };
29
29
  };
30
30
  // transcript に記録された作業ディレクトリ(重複は畳み、最後に居た場所も返す)。
31
+ // Claude Code は各行のトップレベル `cwd`、Codex は先頭行 session_meta の `payload.cwd`。
31
32
  // **現在地がそこと違っていても resume 自体は通ってしまう**ので、人間に見せて確認を取る
32
33
  export const recordedCwds = (transcript) => {
33
34
  const all = [];
@@ -37,7 +38,8 @@ export const recordedCwds = (transcript) => {
37
38
  continue;
38
39
  let cwd;
39
40
  try {
40
- cwd = JSON.parse(line).cwd;
41
+ const parsed = JSON.parse(line);
42
+ cwd = parsed.cwd ?? parsed.payload?.cwd;
41
43
  }
42
44
  catch {
43
45
  continue;
@@ -64,6 +66,8 @@ export const workspaceRepoName = (cwds) => {
64
66
  return null;
65
67
  };
66
68
  const projectsRoot = () => join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects");
69
+ // Codex の設定ルート(CLI 自身が CODEX_HOME を見るのでそれに合わせる)
70
+ const codexHome = () => process.env.CODEX_HOME ?? join(homedir(), ".codex");
67
71
  // 置き先のディレクトリを **規則を再実装せず観測で決める**(agent-runner の
68
72
  // _helpers/session.ts と同じ方針)。~/.claude/projects/*/*.jsonl の cwd を読み、
69
73
  // いま居るディレクトリと一致するものを選ぶ
@@ -113,25 +117,38 @@ export const downloadTranscript = async (url) => {
113
117
  }
114
118
  return await res.text();
115
119
  };
116
- // 置き先へ書き込む。ディレクトリが見つからないのは「この repo でまだ claude を
117
- // 起動していない」ケースなので、観測できるようにしてから再実行してもらう
118
- export const placeTranscript = (cwd, sessionId, transcript) => {
119
- const dir = findProjectDir(cwd);
120
- if (!dir) {
121
- throw new Error("このディレクトリの Claude Code プロジェクトが見つかりません。" +
122
- "ここで一度 `claude` を起動してから、もう一度実行してください。");
120
+ // 置き先へ書き込む。
121
+ // Claude Code は cwd から導出したディレクトリしか見ないので、現在地に対応する project dir を
122
+ // 観測で引き当てて置く。見つからないのは「この repo でまだ claude を起動していない」ケースなので、
123
+ // 観測できるようにしてから再実行してもらう。
124
+ // Codex は cwd に依らず sessions/ 配下を id で探すので、退避時の相対パスにそのまま置く
125
+ export const placeTranscript = (cwd, ref, transcript) => {
126
+ let file;
127
+ if (ref.agent === "codex") {
128
+ file = join(codexHome(), ref.local_path);
123
129
  }
124
- mkdirSync(dir, { recursive: true });
125
- const file = join(dir, `${sessionId}.jsonl`);
130
+ else {
131
+ const dir = findProjectDir(cwd);
132
+ if (!dir) {
133
+ throw new Error("このディレクトリの Claude Code プロジェクトが見つかりません。" +
134
+ "ここで一度 `claude` を起動してから、もう一度実行してください。");
135
+ }
136
+ file = join(dir, `${ref.session_id}.jsonl`);
137
+ }
138
+ mkdirSync(dirname(file), { recursive: true });
126
139
  writeFileSync(file, transcript);
127
140
  return file;
128
141
  };
129
- // stdio を引き継いで claude を起動する(そのまま対話に入る)
130
- export const spawnClaudeResume = (sessionId) => new Promise((resolve) => {
131
- const child = spawn("claude", ["--resume", sessionId], { stdio: "inherit" });
142
+ // エージェントごとの対話 resume コマンド
143
+ export const resumeCommand = (agent, sessionId) => agent === "codex"
144
+ ? { bin: "codex", args: ["resume", sessionId] }
145
+ : { bin: "claude", args: ["--resume", sessionId] };
146
+ // stdio を引き継いで CLI を起動する(そのまま対話に入る)
147
+ export const spawnResume = (agent, sessionId) => new Promise((resolve) => {
148
+ const { bin, args } = resumeCommand(agent, sessionId);
149
+ const child = spawn(bin, args, { stdio: "inherit" });
132
150
  child.on("error", () => {
133
- console.error("claude を起動できませんでした。Claude Code をインストールしてから `claude --resume " +
134
- `${sessionId}\` を実行してください。`);
151
+ console.error(`${bin} を起動できませんでした。インストールしてから \`${bin} ${args.join(" ")}\` を実行してください。`);
135
152
  resolve(1);
136
153
  });
137
154
  child.on("close", (code) => resolve(code ?? 0));
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { dirname } from "node:path";
3
3
  import { requireCredentials } from "./credentials.js";
4
- import { downloadTranscript, fetchTranscriptRef, parseStepUrl, placeTranscript, recordedCwds, spawnClaudeResume, workspaceRepoName, } from "./resumeSession.js";
4
+ import { downloadTranscript, fetchTranscriptRef, parseStepUrl, placeTranscript, recordedCwds, spawnResume, workspaceRepoName, } from "./resumeSession.js";
5
5
  // ホーム配下は ~ に畳む(置き先は必ずホーム配下なので実質いつも効く)
6
6
  const tilde = (path) => {
7
7
  const home = homedir();
@@ -9,8 +9,9 @@ const tilde = (path) => {
9
9
  };
10
10
  // `mawaru session resume <step URL>`
11
11
  //
12
- // **確認は取らない。** 書くのは ~/.claude/projects/<現在地>/<sessionId>.jsonl の新規1ファイルだけで
13
- // repo には触れないので、場所を間違えても別プロジェクトの履歴に混ざるだけ=やり直せる。
12
+ // **確認は取らない。** 書くのはエージェントの設定ディレクトリ(~/.claude/projects/<現在地>/ か
13
+ // ~/.codex/sessions/<日付>/)への新規1ファイルだけで repo には触れないので、場所を間違えても
14
+ // 別プロジェクトの履歴に混ざるだけ=やり直せる。
14
15
  // **どこに cd すべきかは mawaru には分からない**(AI ノードの実行 repo と、エージェントが実際に
15
16
  // 触った repo は別物で、後者は setup.sh が workspace/ 配下に clone した対象 repo)ので推定して
16
17
  // 移動もしない。手がかりとして対象 repo 名だけ見せ、現在地と食い違うときに警告する
@@ -25,7 +26,7 @@ export const runSessionResume = async (stepUrl) => {
25
26
  const credentials = requireCredentials();
26
27
  const transcriptRef = await fetchTranscriptRef(credentials, ref);
27
28
  const transcript = await downloadTranscript(transcriptRef.url);
28
- console.log(`取得: セッション ${transcriptRef.session_id.slice(0, 8)}`);
29
+ console.log(`取得: セッション ${transcriptRef.session_id.slice(0, 8)}(${transcriptRef.agent})`);
29
30
  if (transcriptRef.redacted) {
30
31
  console.log(`(退避時に secret を ${transcriptRef.redacted} 箇所マスクしています)`);
31
32
  }
@@ -37,7 +38,7 @@ export const runSessionResume = async (stepUrl) => {
37
38
  console.log(`⚠ 現在地に ${repo} が含まれません。別 repo で再開しようとしているかもしれません(このまま続けます)`);
38
39
  }
39
40
  }
40
- const file = placeTranscript(cwd, transcriptRef.session_id, transcript);
41
+ const file = placeTranscript(cwd, transcriptRef, transcript);
41
42
  console.log(`復元先: ${tilde(dirname(file))}\n再開します…`);
42
- return await spawnClaudeResume(transcriptRef.session_id);
43
+ return await spawnResume(transcriptRef.agent, transcriptRef.session_id);
43
44
  };
@@ -1,5 +1,23 @@
1
1
  import { z } from "zod";
2
- export declare const AI_MODELS: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
2
+ export declare const AI_MODEL_GROUPS: {
3
+ readonly claude: {
4
+ readonly name: "Claude Code";
5
+ readonly models: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
6
+ readonly default: "claude-opus-5";
7
+ };
8
+ readonly codex: {
9
+ readonly name: "Codex";
10
+ readonly models: readonly ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"];
11
+ readonly default: "gpt-6-astra";
12
+ };
13
+ };
14
+ export declare const AI_AGENT_IDS: readonly ["claude", "codex"];
15
+ export declare const aiAgentIdSchema: z.ZodEnum<{
16
+ claude: "claude";
17
+ codex: "codex";
18
+ }>;
19
+ export type AiAgentId = z.infer<typeof aiAgentIdSchema>;
20
+ export declare const AI_MODELS: readonly ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5", "gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"];
3
21
  export declare const aiModelSchema: z.ZodEnum<{
4
22
  "claude-fable-5-1": "claude-fable-5-1";
5
23
  "claude-fable-5": "claude-fable-5";
@@ -7,9 +25,15 @@ export declare const aiModelSchema: z.ZodEnum<{
7
25
  "claude-opus-4-8": "claude-opus-4-8";
8
26
  "claude-sonnet-5": "claude-sonnet-5";
9
27
  "claude-haiku-4-5": "claude-haiku-4-5";
28
+ "gpt-6-astra": "gpt-6-astra";
29
+ "gpt-5.6-sol": "gpt-5.6-sol";
30
+ "gpt-5.6-terra": "gpt-5.6-terra";
31
+ "gpt-5.6-luna": "gpt-5.6-luna";
32
+ "gpt-5.5": "gpt-5.5";
10
33
  }>;
11
34
  export type AiModel = z.infer<typeof aiModelSchema>;
12
- export declare const DEFAULT_AI_MODEL = "claude-opus-5";
35
+ export declare const DEFAULT_AI_MODEL: "claude-opus-5";
36
+ export declare const agentOfModel: (model: AiModel) => AiAgentId;
13
37
  export declare const aiInputDeclSchema: z.ZodObject<{
14
38
  schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
15
39
  resume: z.ZodOptional<z.ZodBoolean>;
@@ -26,6 +50,15 @@ export declare const aiManifestSchema: z.ZodObject<{
26
50
  "claude-opus-4-8": "claude-opus-4-8";
27
51
  "claude-sonnet-5": "claude-sonnet-5";
28
52
  "claude-haiku-4-5": "claude-haiku-4-5";
53
+ "gpt-6-astra": "gpt-6-astra";
54
+ "gpt-5.6-sol": "gpt-5.6-sol";
55
+ "gpt-5.6-terra": "gpt-5.6-terra";
56
+ "gpt-5.6-luna": "gpt-5.6-luna";
57
+ "gpt-5.5": "gpt-5.5";
58
+ }>>;
59
+ auth_mode: z.ZodOptional<z.ZodEnum<{
60
+ subscription: "subscription";
61
+ api: "api";
29
62
  }>>;
30
63
  skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
31
64
  env: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -1,18 +1,48 @@
1
1
  import { z } from "zod";
2
2
  import { jsonSchemaSchema, PORT_KEY_PATTERN } from "./node.js";
3
- // AI ノードのモデル選択肢(既定は Opus:下書き品質が価値の中心。費用はユーザー持ち)。
4
- // エイリアス("opus" 等)は採らずバージョンを固定する:実行のたびに黙って別モデルへ
5
- // 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する
3
+ // AI ノードの実行主体(エージェント CLI)は Claude Code と Codex の2つ。Claude Code は Claude の
4
+ // モデルだけ、Codex は OpenAI のモデルだけを走らせるので、モデル ID と実行主体は 1:1。
5
+ // config.json に agent のようなフィールドは持たず、model の接頭辞(claude-* / gpt-*)で決まる
6
+ // (runner の _agents/index.ts と同じ規則。AI実行エージェントの抽象化(Claude CodeとCodex).md)。
7
+ //
8
+ // モデルはエイリアス("opus" 等)を採らずバージョンを固定する:実行のたびに黙って別モデルへ
9
+ // 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する。
10
+ // 既定は Opus:下書き品質が価値の中心。費用はユーザー持ち
11
+ export const AI_MODEL_GROUPS = {
12
+ claude: {
13
+ name: "Claude Code",
14
+ models: [
15
+ "claude-fable-5-1",
16
+ "claude-fable-5",
17
+ "claude-opus-5",
18
+ "claude-opus-4-8",
19
+ "claude-sonnet-5",
20
+ "claude-haiku-4-5",
21
+ ],
22
+ default: "claude-opus-5",
23
+ },
24
+ codex: {
25
+ name: "Codex",
26
+ models: [
27
+ "gpt-6-astra",
28
+ "gpt-5.6-sol",
29
+ "gpt-5.6-terra",
30
+ "gpt-5.6-luna",
31
+ "gpt-5.5",
32
+ ],
33
+ default: "gpt-6-astra",
34
+ },
35
+ };
36
+ export const AI_AGENT_IDS = ["claude", "codex"];
37
+ export const aiAgentIdSchema = z.enum(AI_AGENT_IDS);
6
38
  export const AI_MODELS = [
7
- "claude-fable-5-1",
8
- "claude-fable-5",
9
- "claude-opus-5",
10
- "claude-opus-4-8",
11
- "claude-sonnet-5",
12
- "claude-haiku-4-5",
39
+ ...AI_MODEL_GROUPS.claude.models,
40
+ ...AI_MODEL_GROUPS.codex.models,
13
41
  ];
14
42
  export const aiModelSchema = z.enum(AI_MODELS);
15
- export const DEFAULT_AI_MODEL = "claude-opus-5";
43
+ export const DEFAULT_AI_MODEL = AI_MODEL_GROUPS.claude.default;
44
+ // モデル ID → 実行主体
45
+ export const agentOfModel = (model) => model.startsWith("claude-") ? "claude" : "codex";
16
46
  // 入力口の宣言:スキーマ+実行属性。resume: true の口から入力が届いた実行は
17
47
  // 同じノードの直近セッションを復元して再開する(backend が ports.resume へ同期して
18
48
  // payload 発行時に参照する。ポート型固定の撤去とセッション再開のポート属性化.md)
@@ -39,6 +69,8 @@ export const aiManifestSchema = z
39
69
  // repo に置く時点で完成品なので必須(DB 時代の「書きかけ空文字」は廃止)
40
70
  prompt: z.string().min(1),
41
71
  model: aiModelSchema.default(DEFAULT_AI_MODEL),
72
+ // 旧定義も編集のため読める。Codex の実行・保存時には明示選択を必須にする
73
+ auth_mode: z.enum(["subscription", "api"]).optional(),
42
74
  // 同 repo のトップレベル skills/ 配下のディレクトリ名
43
75
  skills: z
44
76
  .array(z
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ const USAGE = `使い方: mawaru <command> [引数]
16
16
  login ブラウザで承認して mawaru にログインする(~/.mawaru/credentials.json)
17
17
  logout 保存した資格情報を消す
18
18
  session resume <step URL>
19
- AI ノードのセッションを手元に復元して claude --resume で再開する`;
19
+ AI ノードのセッションを手元に復元して CLI(claude / codex)の resume で再開する`;
20
20
  const [command, ...rest] = process.argv.slice(2);
21
21
  // init / typegen / validate は repo ルートを任意で受ける(省略時はカレント)
22
22
  const root = rest[0] ?? process.cwd();
@@ -46,12 +46,16 @@ skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
46
46
 
47
47
  - `config.json` — 正は `_schemas/ai-manifest.ts`。`prompt`(必須)・`model`・
48
48
  `skills`(トップレベル `skills/` 配下のディレクトリ名)・`env`・`inputs` / `outputs`
49
+ - `model` が実行主体を決める:`claude-*` は Claude Code、`gpt-*` は Codex で走る
50
+ (選択肢は `AI_MODEL_GROUPS`)。認証は repo の Actions secrets に置く:Claude Code は
51
+ `ANTHROPIC_API_KEY`(または `CLAUDE_CODE_OAUTH_TOKEN`)、Codex は明示的な `auth_mode` と対応する認証情報(下記「Codex の認証方式」参照)
52
+ (`env` に宣言しなくても渡される)
49
53
  - `inputs` は1件以上・key 自由。値は `{ "schema": <JSON Schema>, "resume": true }` の形
50
54
  (`resume` は省略可。true の口から入力が届いた実行は、同じノードの直近セッションを
51
55
  復元して再開する——差し戻し→修正の続き実行に使う)。旧形式(値が素の JSON Schema)も
52
56
  読める
53
- - 実行時はラッパーが Claude Code(headless)を起動し、`outputs` のスキーマに適合する
54
- output を検証して報告する。出口が複数あれば AI が1つ選ぶ(複数出口=分岐)
57
+ - 実行時はラッパーがエージェント CLI(Claude Code / Codex、非対話)を起動し、`outputs` の
58
+ スキーマに適合する output を検証して報告する。出口が複数あれば AI が1つ選ぶ(複数出口=分岐)
55
59
  - **文体の既定は「短く」**:出力の文章は承認画面・Slack・顧客への回答としてそのまま人が読むため、
56
60
  ラッパーが枠組みプロンプトで「結論から・前置きや言い換えや締め文句を書かない・1文は短く」を
57
61
  常に指示している。長い文章(詳細なレポート等)が要るノードだけ `prompt` に長さ・文体を明示する
@@ -72,3 +76,55 @@ skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
72
76
 
73
77
  - `npx mawaru validate` — リポジトリ規約の検証(CI 向け。error で exit 1)
74
78
  - config.json を変えたら `npx mawaru typegen` で型を更新し、ズレをコンパイルで検出する
79
+
80
+ ### Codex の認証方式
81
+
82
+ Codex の AI 定義は `auth_mode` を必ず指定する。エディタの「認証方式」で選択できる。
83
+
84
+ - `subscription`:ChatGPT の Codex 利用枠を使う。
85
+ - `api`:OpenAI の API キーで従量課金する。
86
+
87
+ 既存の Codex 定義にも明示的な選択が必要。未指定・認証不足・枠切れ・認証エラーでも別方式へ切り替えない。Claude の認証設定は従来どおり。
88
+
89
+ ```json
90
+ {
91
+ "prompt": "入力に返信してください",
92
+ "model": "gpt-5.6-luna",
93
+ "auth_mode": "subscription",
94
+ "inputs": { "in": { "schema": {} } },
95
+ "outputs": { "main": { "type": "string" } }
96
+ }
97
+ ```
98
+
99
+ API を選ぶ場合は実行 repo の Actions Secret に `OPENAI_API_KEY` または `CODEX_API_KEY` を登録する。両方ある場合は Codex 固有の `CODEX_API_KEY` を使う。
100
+
101
+ #### サブスクを Actions で使う
102
+
103
+ 非公開の実行 repo で、更新した `mawaru-runner.yml` を `npx @mawaru/sdk init` で反映する。
104
+
105
+ 1. 自動実行専用の Codex ログインをファイル形式で作る。日常利用のログインキャッシュを複数の repo・マシンへ配布しない。
106
+ 2. GitHub の実行 repo に `mawaru-codex` Environment を作成する。
107
+ 3. **Environment Secret** に `CODEX_AUTH_JSON`(ログインキャッシュの内容)と `CODEX_AUTH_WRITE_TOKEN`(その repo の Environment Secrets を更新できるトークン)を登録する。標準の `GITHUB_TOKEN` には Secret 更新権限がない。Fine-grained PAT では対象 repo を限定し、Environments の書き込み権限を付ける。
108
+ 4. AI 定義で `auth_mode: "subscription"` を選ぶ。
109
+
110
+ 認証ファイルを表示せず登録する例(`OWNER/REPO` は実行 repo に置き換える):
111
+
112
+ ```sh
113
+ # CODEX_HOME を普段のログインと分けて、初回ログインする
114
+ CODEX_HOME="$HOME/.mawaru-codex" codex -c 'cli_auth_credentials_store="file"' login
115
+ # 値をコマンド引数・履歴・チャットへ貼り付けない
116
+ gh secret set CODEX_AUTH_JSON --repo OWNER/REPO --env mawaru-codex < "$HOME/.mawaru-codex/auth.json"
117
+ gh secret set CODEX_AUTH_WRITE_TOKEN --repo OWNER/REPO --env mawaru-codex
118
+ ```
119
+
120
+ runner は認証を作業ディレクトリ外に展開し、Codex が更新した認証を実行の成否にかかわらず Environment Secret へ戻す。同じ repo の AI ジョブは直列に実行する。GitHub の待機キューは最大100件で、それを超える投入は避ける。repo Secret はキュー投入時の古い値を読むため、サブスク認証の保存先には使わない。実行中の強制キャンセル・書き戻し失敗・ログイン失効時は、新しくログインして `CODEX_AUTH_JSON` を再登録する。
121
+
122
+ API 選択時はサブスク認証を読み込まず、サブスク選択時は API キーを Codex に渡さない。サブスク認証は利用枠無制限を意味しない。追加購入済みクレジットの消費は ChatGPT アカウント側の設定に従う。
123
+
124
+ #### ローカルで使う
125
+
126
+ `auth_mode: "subscription"` を設定したうえで、`CODEX_AUTH_FILE` に自動実行用の auth.json の絶対パスを指定して runner を実行する。更新済み認証は同じファイルへ戻すため、親ディレクトリにも書き込み権限が必要。同じファイルを使う mawaru の並行実行はエラーで止める。
127
+
128
+ API 選択時は `OPENAI_API_KEY` / `CODEX_API_KEY` を runner の環境変数、または既存の `SECRETS_JSON` 経由で渡す。
129
+
130
+ 参考:[Codexの認証](https://learn.chatgpt.com/docs/auth)、[CIでの認証維持](https://learn.chatgpt.com/docs/auth/ci-cd-auth)、[GitHubのSecret読み込み時点](https://docs.github.com/en/actions/reference/security/secrets)、[待機キューの上限](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mawaru/sdk",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -110,7 +110,7 @@ AI ノードの出力の文章は、承認画面・Slack・顧客への回答と
110
110
  「どこに何が置いてあるか」と、そこから先の判断だけを書く。
111
111
 
112
112
  理由:LLM にやらせるとトークンを食い flaky で、失敗が「セットアップの失敗」として明瞭に出ない。
113
- skill の配置は Claude Code 起動前に完了している必要もある。
113
+ skill の配置はエージェント CLI(Claude Code / Codex)の起動前に完了している必要もある。
114
114
 
115
115
  規約(実行 repo 側。config.json への宣言は不要、ファイルの存在が正):
116
116
 
@@ -122,7 +122,7 @@ cp -r workspace/repo/.claude/skills/some-skill skills/ # copySkills の前に
122
122
  printf '%s' "$SOME_SA_KEY" > workspace/key.json
123
123
  ```
124
124
 
125
- - 実行順は `env 注入 → input.json 書き出し → setup.sh → copySkills → 本体(Claude Code)`。
125
+ - 実行順は `env 注入 → input.json 書き出し → setup.sh → copySkills → 本体(エージェント CLI)`。
126
126
  `bash nodes/ai/<dir>/setup.sh "$INPUT_JSON"`(実行ビット不要)、cwd は checkout ルート。
127
127
  - **入力は `$1`(JSON 文字列)か `input.json`** で参照できる(`jq` はランナー同梱)。
128
128
  - **環境変数は config.json の `env` 宣言分だけ**が注入される。必要な secret は `env` に宣言し、
@@ -20,6 +20,15 @@ on:
20
20
  jobs:
21
21
  run:
22
22
  runs-on: ubuntu-latest
23
+ # 認証の更新を次のジョブ開始時に読むため、repo Secret ではなく環境Secretを使う。
24
+ environment:
25
+ name: mawaru-codex
26
+ deployment: false
27
+ # 同じ repo の AI ジョブを直列化。通常の concurrency は待機中ジョブを置換するため max。
28
+ concurrency:
29
+ group: ${{ github.event.client_payload.agent && 'mawaru-ai-auth' || github.run_id }}
30
+ cancel-in-progress: false
31
+ queue: max
23
32
  steps:
24
33
  # 自分の Actions run の URL を mawaru に自己申告する(画面のリンク表示用)。
25
34
  # repository_dispatch は起動した run の ID を返さないため、URL を知っているのは
@@ -48,14 +57,17 @@ jobs:
48
57
 
49
58
  - name: write payload.json
50
59
  run: echo '${{ toJSON(github.event.client_payload) }}' > payload.json
51
- # SECRETS_JSON は全 repo secrets を注入するが、子プロセス(handler / Claude Code)には
60
+ # SECRETS_JSON は全 repo secrets を注入するが、子プロセス(handler / エージェント CLI)には
52
61
  # ラッパーが config.json の env 宣言分だけを展開して渡す(最小権限)
53
62
  # 実行時間の上限はラッパーが config.json の timeout_minutes(既定:program 30分 /
54
63
  # ai 360分)で締めて理由付きの failed を報告する。ここでは縛らず、暴走の最終
55
64
  # backstop は GHA のジョブ上限(6時間)に任せる。step の停止は mawaru の run キャンセル
56
65
  - name: run
57
66
  id: run
58
- env: { SECRETS_JSON: "${{ toJSON(secrets) }}" }
67
+ env:
68
+ SECRETS_JSON: "${{ toJSON(secrets) }}"
69
+ MAWARU_CODEX_AUTH_STORAGE: github-environment
70
+ MAWARU_CODEX_PRIVATE_REPO: "${{ github.event.repository.private }}"
59
71
  run: npx --yes "${{ github.event.client_payload.runner }}" payload.json
60
72
 
61
73
  # ラッパー自体が起動できなかった事故(npx 失敗等)の backstop。