mcpField
F
forge-connector

forge-connector

github.com/atlassian開発27418 回インストール1 週間前に更新
リポジトリ

forge-connector について

forge-connector は、外部データを Teamwork Graph に取り込み、Rovo Search と Rovo Chat で検索可能にする Atlassian Forge の graph:connector アプリを構築するスキルです。開発者向けに、オブジェクト種別やアクセス制御の検討から、ひな型作成、同期処理まで案内します。

主な機能

  1. 01コネクターアプリのひな型生成
  2. 02Teamwork Graph のオブジェクト種別の選定
  3. 03認証情報とフォーム設定の案内
  4. 04アクセス制御とデータ権限の設計
  5. 05定期的な再取り込みの設定

活用シーン

  1. 01外部システムのコンテンツを Rovo 検索に追加
  2. 02ソース側の権限を検索結果に反映
  3. 03接続データの定期更新を設定

MCP 対応

スキルでは、マニフェスト構文、モジュール設定、デプロイ手順の参照先として Forge MCP を挙げています。Custom UI を追加する場合は、Atlaskit コンポーネントの参照に ADS MCP を利用します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/atlassian/forge-skills --skill forge-connector

インストール後、Claude Code で/forge-connectorを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
scripts/
__init__.py
forge_env.py
scaffold_connector.py
tests/
__init__.py
test_forge_env.py
test_scaffold_connector.py

詳細

Forge コネクター

外部データを Atlassian の Teamwork Graph に取り込み、Rovo Search と Rovo Chat に表示する graph:connector Forge アプリを構築します。

重要なルール

  1. Jira へのインストールが必須です — Teamwork Graph モジュールを使用するアプリは、Jira サイトにインストールする必要があります。Confluence のみにインストールしても動作しません。
  2. チャットで認証情報を尋ねないでください — ユーザー自身のターミナルで forge login を実行するよう案内してください。
  3. 必ず自分でスキャフォールドスクリプトを実行してください — 手動の手順を案内するだけでなく、scripts/scaffold_connector.py を実行してボイラープレートを生成してください。
  4. インストールが必要な場合は、必ずユーザーに Atlassian サイトの URL を尋ねてください — URL を自分で調べたり、推測したりしないでください。
  5. 切断すると Atlassian がデータを削除します — action = 'DELETED' の場合、アプリはローカルの状態をクリーンアップするだけでかまいません。Teamwork Graph のデータは Atlassian が自動的に削除します。
  6. ハンドラーの引数は直接渡されます — Forge はリクエストオブジェクトをハンドラーの最初の引数として渡します。event.payload の下にはネストされません。設定値は event.payload.config ではなく、request.configProperties にあります。これは TypeError: Cannot destructure property of undefined エラーの最も一般的な原因です。
  7. ストレージには @forge/kvs を使用してください — @forge/kvs から kvs をインポートしてください。@forge/storage は使用しないでください。コネクター関数の実行時には、その storage エクスポートが undefined になります。
  8. @forge/teamwork-graph の名前付きエクスポート graph を使用してください — 正しいインポートは const { graph } = require('@forge/teamwork-graph') です。graph.setObjects({ objects, connectionId }) を呼び出してください。setObjects を名前付きエクスポートとして直接インポートしないでください。
  9. validateConnectionHandler は { success, message } を返す必要があります — Error をスローしないでください。拒否する場合は { success: false, message: '...' }、受け入れる場合は { success: true } を返してください。
  10. function 宣言は modules の下に配置してください — manifest.yml では、function: はトップレベルのキーではなく、modules: の下に置くキーです。トップレベルに配置すると lint エラーが発生します。
  11. formConfiguration では type: header を含む form 配列を使用します — fields: や beforeYouBegin: は使用しないでください。正しい形式は form: [{ key, type: header, title, description, properties: [...] }] です。
  12. スコープは read/write/delete:object:jira です — read:object:jira、write:object:jira、delete:object:jira を使用してください。read:graph:teamwork と write:graph:teamwork は無効で、forge lint が失敗します。
  13. このスキルで実行する forge コマンドには ATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connector を設定してください — シェルで実行する forge コマンド(例:forge lint、forge logs、forge deploy)と、代替手段としてユーザーに提示する対話型の forge create コマンドには、この環境変数を付けてください。同梱のスクリプトでは自動的に設定されます。このスキル内で簡略化のために省略されているその他のコマンドも、実行時には追加してください。対象外は forge login と forge tunnel(ユーザーが実行する認証/ライブ開発コマンド)のみです。
  14. setObjects/setUsers/setGroups のレスポンスを無視しないでください — データ取り込みの呼び出しを、結果を確認しないまま実行する処理として扱わないでください。必ず response.success と response.results?.rejected の両方を確認してください。呼び出しが部分的に失敗する場合があります。一部のオブジェクトが拒否され、他のオブジェクトが受け入れられることがあり、rejected.length > 0 でも response.success が true になることがあります。
  15. アクセス制御 ID に一貫性を持たせてください — permissions で USER または GROUP のプリンシパルを使用する場合、id の値は、対応する graph.setUsers()/graph.setGroups() の呼び出しで使用する externalId と完全に一致させる必要があります。ID が一致しないと(例:user-42 と accountId:user-42)、本来アクセスできるユーザーからオブジェクトが見えなくなりますが、そのことは通知されません。
  16. 更新スケジュールにはオーケストレーションを使用してください — ユーザーが定期的なデータ再取り込みを希望する場合は、scheduledTrigger ではなく、graph.scheduleOrUpdateTask と taskRunner ハンドラー(プラットフォーム管理方式)を使用してください。オーケストレーションでは、実行頻度、再試行、ファンアウトが標準で処理されます。プラットフォームのタスク追跡を必要としない軽量な cron スタイルのトリガーをユーザーが明示的に求めている場合に限り、scheduledTrigger を代替として使用してください。
  17. taskId は有効な UUID でなければなりません — scheduleOrUpdateTask と scheduleChildTask のどちらも、task.taskId に UUID 文字列を必要とします。ルートタスクには uuid()(v4)を使用し、子タスクには uuidv5('<scanId>:<parentTaskId>:<itemId>', APP_NAMESPACE) を使用してください。ルートタスク ID は KVS に保存し、onConnectionChange を再実行したときに同じ ID を再利用できるようにしてください。これにより、競合するスケジュールが作成されず、既存タスクが更新されます。
  18. スケジュール前に TaskInfo を KVS に書き込み、完了時に削除しないでください — scheduleOrUpdateTask または scheduleChildTask を呼び出す前に、必ず taskId をキーとして TaskInfo レコードを KVS に書き込んでください。成功したら、レコードの completed: true を設定し、削除しないでください。削除すると、重複配信で ENTITY_NOT_FOUND が返され、先に記録された成功が上書きされます。
  19. オーケストレーションのレスポンスでは response.error で分岐してください — scheduleOrUpdateTask と scheduleChildTask は、成功時に { success: true } ではなく { status: 'ACCEPTED' } を返します。失敗を検出するには if (response.error) を確認し、if (!response.success) は確認しないでください。

MCP の前提条件

MCP サーバー目的
Forge MCPマニフェスト構文、モジュール設定、デプロイガイド
ADS MCPAtlaskit コンポーネント(カスタム UI を追加する場合のみ)

エージェントのワークフロー — ステップ 0~7 を順番に完了してください

ステップ 0:前提条件

Node.js(node -v、22 以上が必要)、Forge CLI(forge --version)、ログイン状態(forge whoami)を確認します。不足しているツールをインストールします:

npm install -g @forge/cli

認証されていない場合は、ターミナルで forge login を実行するようユーザーに伝えます。

ステップ 1:開発者スペースを見つける

注: Forge CLI 12.x には forge developer-spaces list が存在しません。非対話形式では開発者スペースの一覧を取得できません。

forge create では、開発者スペースを選択するためにインタラクティブな TTY が必要です。ユーザー自身で実行するよう依頼します:

Tell the user:
  cd <parent-directory>
  ATL_FORGE_ATTRIBUTION_SKILL_NAME=forge-connector forge create --template blank <app-name>

  When prompted, select a Developer Space and let it complete.
  Come back when done.

scaffold スクリプトの --dev-space-id フラグは省略可能で、指定しなくても構いません。指定されていない場合にスクリプトがこのフラグをスキップするよう、更新されています。

ステップ 1.5:データを見つけてオブジェクトタイプにマッピングする

スキャフォールディングの前に実施してください。 適切な Teamwork Graph オブジェクトタイプを特定するため、ユーザーに次の質問をします。atlassian:document を前提としたり、デフォルトとして使用したりしないでください。

ユーザーへの質問
  1. どの外部システムまたはツールに接続しますか? 例:Google Drive、ServiceNow、Salesforce、GitHub、Confluence、Slack、Figma、Zendesk

  2. Rovo でどのようなコンテンツを検索可能にしたいですか? 特定に役立つ例を挙げて質問してください。

    • ファイル、ページ、Wiki 記事、レポート、PDF → atlassian:document が候補です
    • タスク、チケット、課題、バグ、ストーリー → atlassian:work-item が候補です
    • チャットメッセージ、メール、コメント → atlassian:message または atlassian:comment が候補です
    • プロジェクト、ワークスペース、ボード → atlassian:project が候補です
    • コードリポジトリ → atlassian:repository が候補です
    • プルリクエスト/マージリクエスト → atlassian:pull-request が候補です
    • Git コミット → atlassian:commit が候補です
    • デザインファイル(Figma、Sketch)→ atlassian:design が候補です
    • 動画録画 → atlassian:video が候補です
    • カレンダーイベント、会議 → atlassian:calendar-event が候補です
    • スレッド、チャンネル → atlassian:conversation が候補です
    • 顧客アカウントまたは組織 → atlassian:customer-organization が候補です
    • チームスペースまたは組織単位 → atlassian:space が候補です
  3. コンテンツは単一の種類ですか、それとも複数の種類が混在していますか? 混在している場合(例:タスクとドキュメントの両方を扱うプロジェクト管理ツール)は、それぞれを独自のオブジェクトタイプとして取り込む計画を立ててください。スキャフォールドは主要なタイプを 1 つサポートしています。後から manifest.yml に objectTypes エントリを追加できます。

  4. 接続時に管理者が認証情報(API キー、URL、OAuth トークン)を入力する必要がありますか? 必要な場合 → スキャフォールドコマンドで --has-form-config を使用します。 不要な場合(データがすべて Atlassian 内から取得される場合)→ このフラグを省略します。

  5. ソースデータはどのくらいの頻度で変更されますか? これにより同期の頻度が決まります。プラットフォームによるすべての更新には、オーケストレーション(scheduleOrUpdateTask + taskRunner)を使用してください。これにより、頻度、再試行、ファンアウトがネイティブに処理されます。軽量な cron をタスク追跡なしで使用する必要が明確にある場合に限り、従来の scheduledTrigger 方式を使用してください。

    頻度オーケストレーションの間隔
    1 時間ごとscheduleInterval: { value: 1, timeUnit: 'hour' }
    毎日scheduleInterval: { value: 1, timeUnit: 'day' }
    N 分ごと(1~60)scheduleInterval: { value: N, timeUnit: 'minute' }
    静的/一度のみスケジュールされた更新は不要です。onConnectionChange で一度取り込みます

    選択した間隔を記録してから、ステップ 2 に進んでください。

  6. Rovo Search で取り込んだコンテンツを閲覧できるのは誰ですか? これにより、各オブジェクトの permissions.accessControls が決まります。次の質問をしてください。

    • 「このコンテンツはすべて公開されていますか、それともソースシステムで閲覧できるユーザーが制限されていますか?」
    • 「Rovo Search の検索結果にも、ソースシステムの権限を反映させますか?」

    回答を適切なプリンシパルモデルに対応させてください。

    ソースシステムのアクセスモデル使用する accessControls
    制限のない公開コンテンツprincipals: [{ type: 'EVERYONE' }]
    特定の名前付きユーザーがアクセス可能principals: [{ type: 'user', id: '<atlassian-account-id>' }] — ユーザーごとに 1 エントリ
    チームまたはグループベース(例:Confluence スペース、Google Workspace グループ)principals: [{ type: 'group', id: '<group-id>' }] — グループごとに 1 エントリ
    非公開/所有者のみ所有者の Atlassian アカウント ID を指定した user プリンシパルを 1 つ
    混在(ソースのオブジェクトごとの ACL)取り込み時に各アイテムの ACL を取得し、それぞれを user または group プリンシパルに対応させる

    ユーザーがコンテンツは公開されていると明示的に確認しない限り、EVERYONE を既定にしないでください。制限されたコンテンツに EVERYONE を設定すると、Rovo Search で閲覧すべきでないユーザーにデータが漏洩します。

    選択した権限モデルを記録してから、ステップ 2 に進んでください。ステップ 3 で setObjects 呼び出しを記述する際に参照してください。

マッピングの決定

回答に基づいて、以下のオブジェクトタイプの表から最適なタイプを選択します。コンテンツにより適したタイプが本当にない場合(例:任意のファイル添付)に限り、atlassian:document を選択してください。「Rovo でインデックス化」の列で ❌ が付いているタイプ(atlassian:build、atlassian:deployment、atlassian:test)については、これらのオブジェクトは Rovo Search または Rovo Chat に表示されないことをユーザーに警告してください。

ステップ 2 に進む前に、選択したオブジェクトタイプと権限モデルを記録してください。

ステップ 2:コネクターアプリをスキャフォールドする

スキルのディレクトリ(この SKILL.md を含むディレクトリ)から実行します。<object-type> をステップ 1.5 で決定したタイプに置き換えてください。--dev-space-id は省略可能です。

python3 -m scripts.scaffold_connector \
  --name <app-name> \
  --connector-name "<Human Readable Name>" \
  --object-type <object-type> \
  --directory <parent-directory>

前のステップで ID を取得している場合に限り、--dev-space-id <id> を追加してください。

オブジェクトタイプ — ステップ 1.5 で選択したタイプを使用してください。上記の確認質問に回答する前に、既定で atlassian:document を選択しないでください。

フォーム設定フラグ — 管理者が API 認証情報または接続の詳細を入力する必要がある場合(ステップ 1.5 の質問 4 で決定)、--has-form-config を追加してください。Atlassian 内だけで動作するアプリ(外部の認証情報が不要)の場合は省略してください。

forge create に TTY が必要なためスキャフォールドに失敗する場合: スキャフォールドスクリプトは手動で実行する代替コマンドを出力します。ユーザーに対話形式で forge create を実行してもらい、ディレクトリが作成されたらステップ 3 に進んでください。スキャフォールドスクリプトが manifest.yml と src/index.js を書き込むのは、ディレクトリの作成後だけです。

ステップ 3:生成されたコードをカスタマイズする

スキャフォールドの実行後(またはユーザーが対話形式で forge create を実行した後):

cd <app-name>
npm install

空のテンプレートは src/index.js(TypeScript ではなく JavaScript)を生成します。API 呼び出しを追加するために編集してください。スキャフォールドによって動作するハンドラーのひな形が生成されます。ビジネスロジックを追加してください。

編集する主なファイル
ファイル変更内容
src/index.jsfetchExternalData() — API 呼び出しに置き換えます
manifest.yml外部 API がある場合は、その URL を permissions.external.fetch.backend に追加します
package.json依存関係として @forge/api、@forge/kvs、@forge/teamwork-graph を追加します
setObjects — Teamwork Graph にデータを取り込む

graph の名前付きエクスポートを使用してください。setObjects を直接分割代入しないでください。

const { graph } = require('@forge/teamwork-graph');

const result = await graph.setObjects({
  connectionId,          // required — the connectionId from the handler request
  objects: [
    {
      schemaVersion: '1.0',
      id: 'unique-id-from-source',      // unique per connectionId
      updateSequenceNumber: 1,
      displayName: 'My Document Title',
      url: 'https://source-system.example.com/doc/123',
      createdAt: '2024-01-15T10:00:00Z',        // ISO 8601
      lastUpdatedAt: '2024-01-20T14:30:00Z',
      // Use the permission model chosen in Step 1.5 question 6.
      // EVERYONE only if content is confirmed publicly accessible.
      // For user-restricted content: { type: 'USER', id: '<externalId from setUsers>' }
      // For group-restricted content: { type: 'GROUP', id: '<externalId from setGroups>' }
      permissions: [{
        accessControls: [{
          principals: [{ type: 'EVERYONE' }],
        }],
      }],
      'atlassian:document': {
        type: {
          category: 'DOCUMENT',   // see Document Categories table below
          mimeType: 'application/vnd.google-apps.document',
        },
        content: {
          mimeType: 'application/vnd.google-apps.document',
          text: 'document title or snippet for search indexing',
        },
      },
    },
  ],
});

// Always check both success AND rejected — a call can partially fail.
const rejected = result.results?.rejected ?? [];
if (!result.success || rejected.length > 0) {
  console.error('setObjects ingestion failed', {
    connectionId,
    acceptedCount: result.results?.accepted?.length ?? 0,
    rejectedCount: rejected.length,
    rejected,
    error: result.error,
  });
}
  • 1 回の呼び出しにつき最大 100 個のオブジェクト — 大規模なデータセットはループでバッチ処理します
  • id は connectionId ごとに一意である必要があります
  • すべての graph.setObjects() 呼び出しで connectionId が必要です
ドキュメントカテゴリー(atlassian:document.type.category 用)
MIME タイプカテゴリー
application/vnd.google-apps.documentDOCUMENT
application/vnd.google-apps.spreadsheetSPREADSHEET
application/vnd.google-apps.presentationPRESENTATION
application/vnd.google-apps.folderFOLDER
application/pdfPDF
image/*IMAGE
video/*VIDEO
audio/*AUDIO
その他OTHER
getObjectByExternalId — 単一オブジェクトを検索する
const { graph } = require('@forge/teamwork-graph');

const data = await graph.getObjectByExternalId({
  externalId: 'unique-id-from-source',
  objectType: 'atlassian:document',
  connectionId,
});
if (data.success) console.log(data.object);
ステップ 4:デプロイしてインストールする

必ずデプロイスクリプトを実行してください — ユーザーに手動で forge deploy コマンドを実行するよう案内するだけでは不十分です。

デプロイスクリプトはこのスキルではなく、forge-app-builder スキルにあります。この SKILL.md のパスからそのディレクトリを特定してください。2 階層上に移動し(skills/forge-connector/ → skills/)、次に forge-app-builder/ に移動します。以下のすべてのコマンドは、そのディレクトリから実行してください。

# Derive forge-app-builder skill dir from this SKILL.md's path:
# e.g. if this file is at /path/to/skills/forge-connector/SKILL.md
# then the deploy script dir is: /path/to/skills/forge-app-builder/

# If you have the site URL:
python3 -m scripts.deploy_forge_app \
  --app-dir <app-directory> \
  --site <site-url> \
  --product jira

# If you don't have the site URL yet, deploy first then ask:
python3 -m scripts.deploy_forge_app \
  --app-dir <app-directory> \
  --product jira \
  --deploy-only
# Ask: "What is your Atlassian site URL (e.g. yourcompany.atlassian.net)?"
python3 -m scripts.deploy_forge_app \
  --app-dir <app-directory> \
  --site <site-url> \
  --product jira \
  --skip-deps
ステップ 5:Atlassian Administration から接続する

デプロイ後、ユーザーに次の手順を案内してください。

  1. Atlassian Administration → Apps → [site] → Connected apps に移動します
  2. アプリを見つけて、View app details → Connections タブを選択します
  3. コネクターの下にある Connect をクリックします
  4. 設定フィールドがある場合(formConfiguration が定義されている場合)は入力します
  5. Connect をクリックします。これにより action: CREATED を指定して onConnectionChange が呼び出され、データの取り込みが開始されます
ステップ 6:forge tunnel で監視する

開発中に forge tunnel を使うと、コネクター関数の実行時にライブログをターミナルへ直接ストリーミングできます。forge logs を待つ必要がないため、onConnectionChangeHandler、validateConnectionHandler、setObjects の呼び出しで発生したエラーを見つける最も速い方法です。

ユーザー自身のターミナルで実行するよう伝えてください(対話型セッションが必要です)。

cd <app-directory>
forge tunnel

トンネルを起動すると、コネクター関数が呼び出されるたびに(Atlassian Admin で「接続」をクリックする場合や、スケジュールされた再取り込みを実行する場合など)、出力がすぐにストリーミングされます。次の内容を確認してください。

  • [connector] Fetched N items — fetchExternalData() が実行されたことを確認できます
  • [connector] Batch 1: N accepted, 0 rejected — setObjects が成功したことを確認できます
  • validateConnectionHandler からの未捕捉エラーやスローされた例外

トンネルを実行していない場合は、代わりに forge logs を使って過去の呼び出しを確認してください。

# Most recent 50 log lines from development environment
forge logs -e development --limit 50

# Production logs for a specific site
forge logs -e production --site <your-site> --limit 50

トンネルとログ — 使い分け:

状況使用するもの
接続フローを積極的に開発/テストしているforge tunnel — ライブストリーミング
過去の呼び出しや本番環境の問題をデバッグするforge logs
トンネルが捕捉する前にコネクター関数がタイムアウトした--limit 100 を指定した forge logs

注: forge tunnel は、ユーザーが対話型ターミナルで実行する必要があります。エージェント経由で実行しようとしないでください。

ステップ 7:エンドツーエンド検証(任意)

チェックを実行する前に、ユーザーに次のように尋ねてください。

「本番環境へのデプロイ前に、エンドツーエンド検証チェックを実行しますか?接続、取り込み、Rovo Search での表示、権限の境界がすべて正しく機能していることを確認できます。」

ユーザーがいいえと答えた場合、またはスキップを希望した場合は、次に進み、チェックを実行したり説明したりしないでください。 ユーザーがはいと答えた場合は、以下のすべてのチェックを順番に実施してください。

チェック 1 — 接続の確立

Atlassian Administration → Apps → Connected apps で、コネクターのステータスが Connected と表示されていることを確認します。エラーまたは保留中の状態が表示されている場合は、ステップ 6 に戻り、forge tunnel または forge logs の出力を調べてください。

チェック 2 — validateConnection の成功(設定されている場合)

アプリに validateConnection 関数がある場合、Connect をクリックしたときに管理者へ成功メッセージが表示されたことを確認します。表示されていない場合は、ログで戻り値を確認してください。スローされたエラーではなく、{ success: true } が返されている必要があります。

チェック 3 — onConnectionChange の実行と取り込み

forge logs またはトンネルの出力で、次の内容を確認してください。

forge logs -e development --limit 50

次の 3 つの兆候をすべて確認してください。

  • ハンドラーが呼び出された:action: CREATED を含む onConnectionChangeHandler のログ行
  • データが取得された:例:[connector] Fetched N items
  • setObjects が成功した:例:[connector] Batch 1: N accepted, 0 rejected

setObjects が { success: false } を返した場合、エラーの詳細は result.error にあります。ユーザーに伝え、続行する前に修正してください。

チェック 4 — Rovo Search でのオブジェクトの表示
  1. Jira サイトで Rovo Search を開きます(取り込み後、インデックス作成に最大 5 分かかります)
  2. 取り込まれたオブジェクトの少なくとも 1 つの displayName またはコンテンツテキストに含まれる単語を検索します
  3. コネクターのニックネーム(接続時に管理者が設定)で絞り込みます

コネクターから少なくとも 1 件の結果が表示されるはずです。5 分経っても何も表示されない場合は、次の内容を確認してください。

  • setObjects のログに N accepted が記録され、N > 0 であること(チェック 3)
  • オブジェクトの permissions がログイン中のユーザーに一致すること(EVERYONE プリンシパル、またはテストユーザーを含む user/group プリンシパル)
  • manifest.yml に write:object:jira スコープが含まれており、スコープを変更した場合はアプリが再デプロイされていること
チェック 5 — 権限境界(EVERYONE を使用した場合のみスキップ)

コネクターで user または group のプリンシパルを使用している場合:

  1. アクセス権があるはずのユーザーとしてログインし、オブジェクトが Rovo Search に表示されることを確認します
  2. アクセス権がないはずのユーザーとしてログインし、オブジェクトが表示されないことを確認します

制限されたオブジェクトが権限のないユーザーに表示される場合は、setObjects の accessControls プリンシパルを再確認して、再デプロイしてください。

チェック 6 — Rovo Chat がコネクターのデータを参照する

取り込んだコンテンツにのみ答えが含まれる質問を Rovo Chat にします。例:

「[取り込んだアイテムのタイトル] のステータスは何ですか?」

Rovo Chat はコネクターをソースとして引用するはずです。コンテンツが見つからない場合、チェック 3 と 4 に未解決の問題がある可能性が高いです。

チェック 7 — スケジュールされた再取り込みが実行される(設定している場合)

scheduledTrigger を追加した場合:

  1. 一時的に manifest.yml の interval: fiveMinutes を設定して再デプロイし、1 サイクル待ちます
  2. forge logs に refreshIngestionHandler による新しい取り込み実行が表示されることを確認します
  3. 本番環境に移行する前に、元の間隔に戻して再デプロイします
本番環境への準備完了ゲート

ユーザーが検証の実行を選択した場合、該当するすべてのチェックに合格してから本番環境にデプロイ(forge deploy -e production)してください:

チェック本番環境で必須
1 — 接続の確立常に
2 — validateConnection の合格validateConnection が設定されている場合のみ
3 — エラーなしで取り込みが実行された常に
4 — オブジェクトが Rovo Search に表示される常に
5 — 権限境界user/group プリンシパルを使用する場合のみ
6 — Rovo Chat がコネクターを引用する常に
7 — スケジュールされた再取り込みが実行されるscheduledTrigger が設定されている場合のみ

マニフェストリファレンス

重要なルール:

  • スコープは read:object:jira、write:object:jira、delete:object:jira です。read:graph:teamwork/write:graph:teamwork ではありません(これらは forge lint で失敗します)
  • function: はトップレベルではなく、modules: の下に宣言します
  • エグレスでは単なる文字列ではなく address: を使用します(自動修正するには forge lint --fix を実行します)
  • formConfiguration では form: [{ type: header, properties: [...] }] を使用します。fields: や beforeYouBegin: ではありません
最小限のコネクター(管理者設定なし、OAuth なし)

アプリが Atlassian 内で完結し、外部の認証情報が不要な場合に使用します。

app:
  id: <generated-by-forge-create>
  runtime:
    name: nodejs24.x
    memoryMB: 256
    architecture: arm64

permissions:
  scopes:
    - read:object:jira
    - write:object:jira
    - delete:object:jira
    - storage:app

modules:
  graph:connector:
    - key: my-connector
      name: My Service
      capabilities:
        replicatesPermissions: false  # true if mirroring source-system ACLs into Teamwork Graph
        syncFidelity: mirror          # append | upsert | mirror (mirror is recommended)
        supportsIncrementalSync: false
      icons:
        light: https://cdn.example.com/logo.png
        dark: https://cdn.example.com/logo.png
      objectTypes:
        - atlassian:document
      datasource:
        onConnectionChange:
          function: on-connection-change

  function:
    - key: on-connection-change
      handler: index.onConnectionChangeHandler
オーケストレーションを備えたコネクター(プラットフォーム管理の更新)

プラットフォーム管理の定期的な再取り込みを有効にするには、任意のコネクター構成に orchestration.taskRunner ブロックを追加します。既存の onConnectionChange ハンドラーと併せて設定します。

modules:
  graph:connector:
    - key: my-connector
      name: My Service
      capabilities:
        replicatesPermissions: false
        syncFidelity: mirror
        supportsIncrementalSync: false
      icons:
        light: https://cdn.example.com/logo.png
        dark: https://cdn.example.com/logo.png
      objectTypes:
        - atlassian:document
      orchestration:
        taskRunner:
          function: task-runner-fn     # references the function key below
      datasource:
        onConnectionChange:
          function: on-connection-change

  function:
    - key: on-connection-change
      handler: index.onConnectionChangeHandler
    - key: task-runner-fn
      handler: index.taskRunner
      timeoutSeconds: 900             # optional: extend to 15 min for large datasets

timeoutSeconds が有効なのは、orchestration.taskRunner のようなコンシューマーモジュールから参照されている関数のみです。参照されていない関数に設定すると、マニフェストの検証に失敗します。

管理者用フォーム設定を備えたコネクター(API キー/URL)

外部システムへの接続に必要な認証情報を管理者が入力する場合に使用します。

app:
  id: <generated-by-forge-create>
  runtime:
    name: nodejs24.x
    memoryMB: 256
    architecture: arm64

permissions:
  scopes:
    - read:object:jira
    - write:object:jira
    - delete:object:jira
    - storage:app
  external:
    fetch:
      backend:
        - address: 'https://api.your-service.com'   # note: address: not a bare string

modules:
  graph:connector:
    - key: my-connector
      name: My Service
      capabilities:
        replicatesPermissions: false  # true if mirroring source-system ACLs into Teamwork Graph
        syncFidelity: mirror          # append | upsert | mirror (mirror is recommended)
        supportsIncrementalSync: false
      icons:
        light: https://cdn.example.com/logo.png
        dark: https://cdn.example.com/logo.png
      objectTypes:
        - atlassian:document
      datasource:
        formConfiguration:
          form:                          # use form:, NOT fields: or beforeYouBegin:
            - key: connectionDetails
              type: header
              title: Connection Details
              description: >
                Provide your My Service API credentials.
                Find them in My Service → Settings → API.
              properties:
                - key: apiKey           # camelCase keys — accessed as request.configProperties.apiKey
                  label: API Key
                  type: string
                  isRequired: true
                - key: apiUrl
                  label: API URL
                  type: string
                  isRequired: true
          validateConnection:
            function: validate-connection
        onConnectionChange:
          function: on-connection-change

  function:                              # function: is under modules:, NOT top-level
    - key: on-connection-change
      handler: index.onConnectionChangeHandler
    - key: validate-connection
      handler: index.validateConnectionHandler

ハンドラーのシグネチャ

重要: Forge はリクエストをラップせずに最初の引数として直接渡します。event.payload の下にはラップされません。フォームの設定値は event.payload.config ではなく request.configProperties にあります。これを誤ると、TypeError: Cannot destructure property of undefined が発生します。

onConnectionChange
const { kvs } = require('@forge/kvs');
const { graph } = require('@forge/teamwork-graph');

exports.onConnectionChangeHandler = async (request) => {
  // request.action, request.connectionId, request.configProperties
  const { action, connectionId, configProperties } = request;

  if (action === 'DELETED') {
    // Atlassian removes Teamwork Graph data automatically on disconnect.
    // Only clean up locally stored credentials.
    await kvs.deleteSecret(connectionId);
    return { success: true };
  }

  // CREATED or UPDATED — persist credentials and ingest data
  await kvs.setSecret(connectionId, configProperties);
  await ingestAllData(connectionId, configProperties);
  return { success: true };
};
validateConnection
const { fetch } = require('@forge/api');

exports.validateConnectionHandler = async (request) => {
  // request.configProperties — NOT event.payload.config
  const { configProperties } = request;

  // Return { success: false, message } to reject — do NOT throw an Error.
  // Return { success: true } to accept.
  const response = await fetch(`${configProperties['apiUrl']}/health`);
  if (!response.ok) {
    return { success: false, message: 'Invalid API credentials. Please check your settings.' };
  }
  return { success: true, message: 'Connection validated successfully.' };
};
refreshIngestion(スケジュールされたトリガー)
exports.refreshIngestionHandler = async () => {
  const activeConnections = await kvs.get('active-connections') ?? [];
  for (const connectionId of activeConnections) {
    const config = await kvs.getSecret(connectionId);
    if (config) await ingestAllData(connectionId, config);
  }
};

オブジェクトタイプ

太字のオブジェクトは、Rovo Search と Rovo Chat でインデックス化されます。

オブジェクトタイプRovo でインデックス化最適な用途
atlassian:document✅ファイル、ページ、Wiki 記事、レポート
atlassian:message✅チャットメッセージ、メール、コメント
atlassian:work-item✅タスク、チケット、課題
atlassian:project✅プロジェクト、ワークスペース
atlassian:space✅チームスペース、組織単位
atlassian:design✅デザインファイル(Figma など)
atlassian:repository✅コードリポジトリ
atlassian:pull-request✅PR、マージリクエスト
atlassian:commit✅Git コミット
atlassian:branch✅Git ブランチ
atlassian:conversation✅スレッド、チャンネル
atlassian:video✅ビデオ録画
atlassian:calendar-event✅ミーティング、イベント
atlassian:comment✅レビューコメント
atlassian:customer-organization✅顧客アカウント、組織
atlassian:build❌CI/CD ビルド
atlassian:deployment❌デプロイ
atlassian:test❌テストケース

Rovo Search / Rovo Chat での表示

取り込みが完了すると:

  • オブジェクトは Rovo Search に、コネクターのニックネーム(接続時に管理者が設定)を名前とするサブフィルターの下に表示されます
  • 取り込んだコンテンツに関連するトピックについて質問すると、Rovo Chat は回答内でコネクターのオブジェクトを参照し、引用できます
  • データはすぐには利用できません。onConnectionChange の実行後、インデックス化に数分かかる場合があります

取り込みが機能していることを確認するには:

  1. Jira サイトで Rovo Search を開きます
  2. 取り込んだオブジェクトの name または properties に含まれるテキストを検索します
  3. コネクターのニックネームでフィルターし、結果を絞り込みます

大規模データセット向けのバッチ処理パターン

const { graph } = require('@forge/teamwork-graph');

const BATCH_SIZE = 100;

async function ingestAllData(connectionId, config) {
  const items = await fetchExternalData(config);

  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batchNum = Math.floor(i / BATCH_SIZE) + 1;
    const batch = items.slice(i, i + BATCH_SIZE);
    const result = await graph.setObjects({
      connectionId,          // required in every call
      objects: batch.map(item => ({
        schemaVersion: '1.0',
        id: item.id,                      // unique per connectionId
        updateSequenceNumber: 1,
        displayName: item.title,
        url: item.url,
        createdAt: item.createdAt,
        lastUpdatedAt: item.updatedAt,
        // Replace with USER/GROUP principals if source system has access controls.
        // The id must exactly match the externalId used in graph.setUsers() / graph.setGroups().
        permissions: [{
          accessControls: [{ principals: [{ type: 'EVERYONE' }] }],
        }],
        'atlassian:document': {
          type: { category: 'DOCUMENT', mimeType: item.mimeType },
          content: { mimeType: item.mimeType, text: item.title },
        },
      })),
    });

    // Always check both success AND rejected — a call can partially fail.
    const rejected = result.results?.rejected ?? [];
    if (!result.success || rejected.length > 0) {
      console.error(`[connector] setObjects batch ${batchNum} failed`, {
        connectionId,
        acceptedCount: result.results?.accepted?.length ?? 0,
        rejectedCount: rejected.length,
        rejected,
        error: result.error,
      });
    } else {
      console.log(`[connector] Batch ${batchNum}: ${result.results?.accepted?.length ?? batch.length} accepted, 0 rejected`);
    }
  }
}

オーケストレーションによる再取り込み(推奨)

ユーザーが更新スケジュールを選択した場合は、オーケストレーションを使用します。プラットフォームは、接続ごとに設定された周期で taskRunner を呼び出すため、外部スケジューラーは不要です。詳しくは、オーケストレーションの概念を参照してください。

タスク ID の生成用に uuid パッケージをインストールします。

npm install uuid
各 taskRunner の呼び出しで受け取る 3 つの ID
ID有効期間用途
taskIdルートタスクの呼び出し間で一定TaskInfo の KVS キー
scanIdルートタスクのスケジュール実行ごとに 1 つ変更せずに updateTaskStatus に渡します。すべての子タスクにも引き継がれます
taskExecutionId呼び出しの試行ごとに固有変更せずに updateTaskStatus に渡します
onConnectionChange — ルートタスクをスケジュールする

CREATED と UPDATED の両方で scheduleOrUpdateTask を呼び出します。同じ taskId を再利用すると、既存のスケジュールがその場で更新されます。

const { graph, types } = require('@forge/teamwork-graph');
const { kvs } = require('@forge/kvs');
const { v4: uuid } = require('uuid');

exports.onConnectionChangeHandler = async (request) => {
  const { action, connectionId, configProperties } = request;

  if (action === 'DELETED') {
    await kvs.deleteSecret(connectionId);
    await kvs.delete(`rootTaskId:${connectionId}`);
    // TaskInfo rows for in-flight tasks are cleaned up here or in a maintenance pass.
    return { success: true };
  }

  await kvs.setSecret(connectionId, configProperties);

  // Mint once, reuse on UPDATED — same taskId makes scheduleOrUpdateTask an update.
  let rootTaskId = await kvs.get(`rootTaskId:${connectionId}`);
  if (!rootTaskId) {
    rootTaskId = uuid();
    await kvs.set(`rootTaskId:${connectionId}`, rootTaskId);
  }

  // Write TaskInfo BEFORE scheduling (taskRunner reads this to dispatch).
  await kvs.set(`taskInfo:${rootTaskId}`, {
    taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
    connectionId,
    createdAt: new Date().toISOString(),
  });

  const response = await graph.scheduleOrUpdateTask({
    connectionId,
    task: {
      taskId: rootTaskId,
      taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
      scheduleInterval: { value: 1, timeUnit: 'day' }, // value: 1–60; timeUnit: minute|hour|day
    },
  });

  // Branch on response.error — NOT response.success (orchestration responses don't set a boolean success field).
  if (response.error) {
    console.error('[connector] scheduleOrUpdateTask failed:', response.error);
    return { success: false, message: response.error };
  }

  return { success: true };
};
taskRunner — 冪等性を保って取り込みを実行する
exports.taskRunner = async (request) => {
  const { taskId, scanId, taskExecutionId, connectionId } = request;

  // Read TaskInfo — dispatch key and idempotency state.
  const taskInfo = await kvs.get(`taskInfo:${taskId}`);
  if (!taskInfo) {
    await graph.updateTaskStatus({
      scanId, taskExecutionId, connectionId,
      status: 'failure',
      failureReason: 'ENTITY_NOT_FOUND',
      task: { taskId },
    });
    return { success: false, message: 'Task not found' };
  }

  // Short-circuit duplicate deliveries — the platform delivers at-least-once.
  if (taskInfo.completed) {
    await graph.updateTaskStatus({ scanId, taskExecutionId, connectionId, status: 'success', task: { taskId } });
    return { success: true, message: 'Already completed (duplicate delivery)' };
  }

  try {
    const config = await kvs.getSecret(connectionId);
    await ingestAllData(connectionId, config);

    // Mark completed BEFORE updateTaskStatus so a crash between the two
    // doesn't cause a duplicate invocation to re-run the full pipeline.
    await kvs.set(`taskInfo:${taskId}`, { ...taskInfo, completed: true });
    await graph.updateTaskStatus({ scanId, taskExecutionId, connectionId, status: 'success', task: { taskId } });
    return { success: true };
  } catch (err) {
    console.error('[connector] taskRunner failed:', err);
    await graph.updateTaskStatus({
      scanId, taskExecutionId, connectionId,
      status: 'failure',
      failureReason: 'INTERNAL_ERROR',
      task: { taskId },
    });
    return { success: false, message: err.message };
  }
};
タスクタイプ(types.FORGE_TASK_TYPES)

taskType は値が限定された列挙型です。生の文字列ではなく、SDK 定数を使用してください。

const { types } = require('@forge/teamwork-graph');

types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL          // full re-sync of objects
types.FORGE_TASK_TYPES.ENTITY_INGESTION_INCREMENTAL   // delta sync (changes only)
types.FORGE_TASK_TYPES.USER_INGESTION_FULL
types.FORGE_TASK_TYPES.USER_INGESTION_INCREMENTAL
types.FORGE_TASK_TYPES.GROUP_INGESTION_FULL
types.FORGE_TASK_TYPES.GROUP_INGESTION_INCREMENTAL
types.FORGE_TASK_TYPES.RELATIONSHIP_INGESTION_FULL
types.FORGE_TASK_TYPES.RELATIONSHIP_INGESTION_INCREMENTAL
子タスクを使ったファンアウト(大規模データセット)

大規模なデータセットでは、ルートのtaskRunnerから各項目の子タスクにファンアウトします。子タスクには決定論的なUUID(uuidv5)を使用し、重複して配信された場合も同じTaskInfo行に解決されるようにします。

const { v5: uuidv5 } = require('uuid');

const APP_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // any stable UUID

// Inside taskRunner, after fetching item list:
for (const item of items) {
  const childTaskId = uuidv5(`${scanId}:${taskId}:${item.id}`, APP_NAMESPACE);

  await kvs.set(`taskInfo:${childTaskId}`, {
    taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
    connectionId,
    createdAt: new Date().toISOString(),
    metadata: { itemId: item.id },
  });

  const response = await graph.scheduleChildTask({
    connectionId,
    scanId,          // must be the current scan's scanId
    task: {
      taskId: childTaskId,
      taskType: types.FORGE_TASK_TYPES.ENTITY_INGESTION_FULL,
    },
  });

  if (response.error) {
    console.error('[connector] scheduleChildTask failed:', response.error);
  }
}

スケジュールされた再取り込み(レガシー — オーケストレーションを推奨)

オーケストレーションを推奨(上のセクション)。ユーザーがプラットフォーム管理の定期同期を希望する場合に使用してください。scheduledTriggerは、プラットフォームによるタスク追跡が不要なシンプルなcron形式のトリガーにのみ使用します。

基本的なスケジュールトリガーでデータを最新の状態に保つには、次のようにします。

# In manifest.yml — under modules:
scheduledTrigger:
  - key: refresh-trigger
    function: refresh-ingestion
    interval: day   # prefer 'day' or 'hour'; avoid 'fiveMinutes'

# Under function:
  - key: refresh-ingestion
    handler: index.refreshIngestionHandler
const { kvs } = require('@forge/kvs');

// Track active connections in onConnectionChangeHandler:
//   await kvs.set('active-connections', [...activeConnections, connectionId]);
//   await kvs.setSecret(connectionId, configProperties);  // store credentials securely

exports.refreshIngestionHandler = async () => {
  const activeConnections = await kvs.get('active-connections') ?? [];
  for (const connectionId of activeConnections) {
    const config = await kvs.getSecret(connectionId);  // retrieve stored credentials
    if (config) await ingestAllData(connectionId, config);
  }
};

スクリプト

スクリプトスキルディレクトリ用途
scripts/scaffold_connector.pyskills/forge-connector/(このスキル)新しいコネクターアプリをひな形から作成します — manifest.yml、src/index.tsを生成し、SDKをインストールします。実行方法:python3 -m scripts.scaffold_connector
scripts/deploy_forge_app.pyskills/forge-app-builder/(別のスキル)Jiraにデプロイしてインストールします。forge-app-builderディレクトリから実行してください:python3 -m scripts.deploy_forge_app

ひな形作成スクリプトはこのスキルのディレクトリにあります。デプロイスクリプトはforge-app-builderスキルのディレクトリにあります。実行前に必ずそこへcdするか、このSKILL.mdの場所からパスを導出してください。


開発のベストプラクティス

よくある問題を避けるため、次のプラクティスに従ってください。詳しくは、公式のコネクター要件をご覧ください。

取り込みエラーを無視しない

setObjects、setUsers、setGroupsからのレスポンスを必ず確認してください。呼び出し全体が失敗する場合だけでなく、一部の項目だけが拒否される場合もあります。一部のオブジェクトが拒否されていても、response.successがtrueになることがあります。

const response = await graph.setObjects({ connectionId, objects });

const rejected = response.results?.rejected ?? [];
if (!response.success || rejected.length > 0) {
  console.error('Teamwork Graph object ingestion failed', {
    connectionId,
    acceptedCount: response.results?.accepted?.length ?? 0,
    rejectedCount: rejected.length,
    rejected,
    error: response.error,
  });
}
アクセス制御IDの整合性を保つ

オブジェクトのpermissionsでUSERまたはGROUPプリンシパルを使用する場合は、まずgraph.setUsers()/graph.setGroups()で対応するユーザーまたはグループを取り込んでください。accessControlsのidは、それらの呼び出しに渡すexternalIdと完全に同じ文字列でなければなりません。IDが一致しないと、本来アクセス権を持つユーザーからオブジェクトが気付かないうちに見えなくなります。

// Ingest user with externalId 'user-42'
await graph.setUsers({ connectionId, users: [{ externalId: 'user-42', displayName: 'Alex Chen', emails: [{ value: '[email protected]', primary: true }] }] });

// Use the same ID 'user-42' in accessControls — NOT 'accountId:user-42' or any other variant
await graph.setObjects({
  connectionId,
  objects: [{
    // ...
    permissions: [{ accessControls: [{ principals: [{ type: 'USER', id: 'user-42' }] }] }],
  }],
});
ケイパビリティを宣言する

graph:connectorモジュールにcapabilitiesブロックを追加してください。これがないと、管理者には設定画面に「開発者によってケイパビリティが宣言されていません」と表示され、信頼性が損なわれます。コネクターの動作を正確に反映した値を設定してください。

プロパティ型値説明
replicatesPermissionsbooleantrue / falseソースシステムのACLをミラーリングするかどうかです。falseの場合、取り込まれたデータはワークスペース内のすべてのユーザーに表示されます。
syncFidelityenumappend / upsert / mirrormirrorを推奨します。新しいオブジェクト、更新、削除を同期します。
supportsIncrementalSyncbooleantrue / false前回の実行以降の変更のみをコネクターが同期するかどうかです。
データ削除の責任
アプローチ動作要件
ミラー(推奨)ソースでの削除が同期されます — Teamwork Graph は常に最新の状態を反映します削除の伝播を実装します
切断のみ削除は同期されません — 管理者が切断するまでデータは保持されますこの制限をコネクターのドキュメントと Marketplace の掲載ページで目立つように説明します

管理者が切断すると、Atlassian は取り込まれたすべてのデータを自動的に削除します。action: DELETED を指定した onConnectionChange ハンドラーでは、アプリ側のリソース(同期ジョブの停止、Webhook の削除、保存済み認証情報の削除)のクリーンアップのみが必要です。


トラブルシューティング

問題対処方法
マニフェストで graph:connector が認識されないforge lint を実行してください。エラーの原因となっているフィールドが正確に特定されます
TypeError: Cannot destructure property 'config' of 'event.payload'ハンドラーが event.payload.config を使用しています。request.configProperties に変更してください。Forge はリクエストを直接渡し、event.payload の下にはネストしません
TypeError: Cannot read properties of undefined (reading 'set')@forge/storage の storage を使用しています。@forge/kvs の kvs に切り替えてください
graph.setObjects is not a functionインポートが誤っています。const { graph } = require('@forge/teamwork-graph') を使用し、graph.setObjects({ objects, connectionId }) を呼び出してください
forge lint:スコープ read/write:graph:teamwork が無効read:object:jira、write:object:jira、delete:object:jira に置き換えてください
forge lint:document should NOT have additional property 'function'function: がトップレベルにあります。modules: の中に移動してください
forge lint:formConfiguration must have required property 'form'fields:/beforeYouBegin: を form: [{ type: header, properties: [...] }] に置き換えてください
forge lint の警告:非推奨の egress エントリーforge lint --fix を実行して、URL 文字列のみの形式を { address: 'url' } に自動変換してください
forge developer-spaces list コマンドが見つからないForge CLI 12.x には存在しません。ユーザーに forge create を対話形式で実行し、developer space を選択してもらってください
forge create が非 TTY エラーで失敗するforge create には対話型ターミナルが必要です。ユーザーに実行を依頼し、その後、作成されたディレクトリにマニフェストとソースファイルを書き込んでください
onConnectionChange がトリガーされない管理者が Atlassian Administration → Connected apps で「Connect」をクリックしたことを確認してください。forge tunnel を実行して関数が呼び出されることを確認してください
Rovo Search にオブジェクトが表示されないインデックス作成に約 5 分かかるため、しばらくお待ちください。forge logs -e development --since 15m を実行して setObjects のエラーを確認してください
setObjects が success: true を返すのに、Rovo にオブジェクトが表示されないresponse.results?.rejected を確認してください。呼び出し全体が成功していても、個々のアイテムが拒否されることがあります。アイテムごとのエラー詳細を確認するには、rejected 配列をログに記録してください
アクセス権を持つユーザーに Rovo Search のオブジェクトが表示されないaccessControls の principal の id が、graph.setUsers()/graph.setGroups() で使用した externalId と完全に一致することを確認してください。ID が一致しない場合(例:user-42 と accountId:user-42)、アクセスに関する問題が通知されないまま発生します
@forge/teamwork-graph の呼び出しで 403 が発生するread:object:jira、write:object:jira、delete:object:jira がマニフェストのスコープに含まれていることを確認し、その後、再デプロイして forge install --upgrade を実行してください
forge login が必要https://id.atlassian.com/manage/api-tokens で API トークンを作成し、forge login を実行してください
scheduleOrUpdateTask が { status: 'ACCEPTED' } を返すのに、コードでは失敗と判定されるresponse.success ではなく response.error に基づいて分岐してください。オーケストレーションの成功レスポンスには、ブール値の success フィールドが設定されません
SDK から task.taskType must be one of: ... が返されるtaskType は選択肢が限定された列挙型です。@forge/teamwork-graph の types.FORGE_TASK_TYPES.* 定数を使用してください。任意の文字列はクライアント側で拒否されます
task.taskId must be a valid UUID formatルートタスクには uuid()(v4)、子タスクには uuidv5(...) を使用してください。"my-app:sync" のような自由形式の文字列は拒否されます
Failed to schedule child tasks: Not Found - Scan not foundscheduleChildTask に渡した scanId がアクティブなスキャンと一致していません。アクティブな taskRunner の実行中に呼び出し、request.scanId を変更せずに渡していることを確認してください
実行が成功した後に taskRunner が ENTITY_NOT_FOUND を報告するTaskInfo を早く削除しすぎています。レコードを削除する代わりに、completed: true フラグを使用して重複配信を早期に処理してください
taskRunner の taskMetadata が空scheduleChildTask の temporalTaskMetadata/persistentTaskMetadata は、呼び出し元に確実に引き継がれるわけではありません。ハンドラーに必要な情報はすべて、TaskInfo.metadata を介して KVS に保存してください
scheduleInterval.value が範囲外というエラー値は 1 から 60 の間である必要があります。値を調整して再デプロイしてください


名前とロゴに関するガイドライン

  • コネクター名には公式サービス名を使用します(例:Drive Connector by AcmeではなくGoogle Drive)
  • アイコンには公式サービスのロゴを使用します。独自のブランド要素を追加したり、組み合わせたりしないでください
  • このガイドラインはgraph:connectorモジュールにのみ適用されます。Forgeアプリ自体には独自のブランド要素を使用できます

コメント