关于 add-table-to-offline-profile
add-table-to-offline-profile 是用于将单个表加入现有 Mobile Offline Profile 的技能,适合已通过 `/add-dataverse` 添加表后配置离线同步的流程。它会处理离线前置条件、范围选择、关系与列配置,并发布配置变更。
主要功能
- 01将表加入现有离线配置文件
- 02检查并按批准范围启用离线标志
- 03配置记录范围、关系和选定列
- 04发布配置文件变更
适用场景
- 01让新添加到应用的 Dataverse 表可离线使用
- 02为特定表配置离线记录范围和同步列
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill add-table-to-offline-profile安装后在 Claude Code 中输入/add-table-to-offline-profile确认已加载。
详细介绍
共享说明:shared-instructions.md — 请先阅读。
参考资料:
- dataverse-offline-api.md §4 + §7 — POST 项 + PATCH selectedcolumns
- offline-profile-reconciliation.md — Step 7 中写入的
schemaColumns基线
将表添加到脱机配置文件
向现有 Mobile Offline Profile 添加单个表。最常见的流程是:用户运行 /add-dataverse 向应用添加新表后,现在希望该表也能脱机使用。
这相当于运行 /setup-offline-profile 并看到现有配置文件(扩展模式),但会跳过配置文件中已有表的逐表问卷 — 只配置新表。
调用上下文和根目录绑定
此内部辅助工具始终作为子调用运行。在读取任何项目内容或执行任何命令之前,
先执行 app-working-directory.md。
要求所有者提供绝对路径 working_dir;如果提供了 --working-dir,则必须
指向同一个原生规范根目录。根目录上下文缺失、路径为相对路径或存在冲突时,
应在访问项目之前返回 NEEDS_CONTEXT;目录无法访问时返回 BLOCKED。绝不使用启动时的 cwd,也不得引入其他根目录解析器。
从所有者接收 MOBILE_APP_ORCHESTRATING=1、orchestrator、working_dir、phase、
approved_scope 和已提供的答案。保持选定的环境 ID/URL/租户、配置文件 ID、
确切的表白名单和已批准的操作不变。范围缺失或存在冲突时返回 NEEDS_CONTEXT;
不得替换为其他环境、配置文件或表。标记不代表已获批准:仅有架构批准,并不授权更改脱机先决条件、
配置文件项、关系、列、发布、删除或成员资格。如果当前已有相同的脱机批准,则仅复用这些批准;否则保留 Step 3–4 的关卡。
对于 --plan-only 或规划阶段的交接,在允许的只读发现之后、任何先决条件更新、
配置文件变更、发布或工件写入之前,返回提案。关卡响应不能解除仅提案模式。
将规范契约应用于每次 shell 调用和文件工具调用,包括版本/身份验证检查、行数统计、屏幕 grep、从参考资料复制的命令、
验证、恢复和重试。每次调用都重新提供绝对路径 PLUGIN_ROOT 和必需的上下文;之前的 cd、导出或 shell 变量不会保留。
使用下面的规范 Bash 防护代码或其 PowerShell 等效代码。将占位符替换为经过验证且经过 shell 引用的调用值;如果缺少值,则返回 NEEDS_CONTEXT,绝不执行未解析的占位符。
文件工具使用绝对路径 <working_dir>/...;插件读取/脚本使用 ${PLUGIN_ROOT}/...。仅在此根目录中解析一次清单位置,并保留该绝对路径;文件缺失时,绝不搜索其他应用。
工作流
- 验证项目 + 定位配置文件 → 2. 解析目标表 → 3. 先决条件检查(如有需要则自动启用) → 4. 范围选择器(单个问题) → 5. POST 项 + PATCH selectedcolumns → 6. 发布 → 7. 更新工件 → 8. 总结
步骤 1 — 验证项目并定位配置文件
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
if [ ! -f power.config.json ] || [ ! -f app.config.js ]; then
echo "BLOCKED: working_dir is not an initialized app" >&2
exit 1
fi
读取 <working_dir>/power.config.json,并要求 environmentId 非空且与所有者选定的环境匹配。复用匹配的完整环境上下文,或从绑定的根目录执行此非持久化查询:
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
environment_id=$(node -p "require('./power.config.json').environmentId || ''") || {
echo "BLOCKED: unreadable power.config.json" >&2; exit 1;
}
if [ -z "$environment_id" ] || [ "$environment_id" != "<selected-environment-id>" ]; then
echo "NEEDS_CONTEXT: selected environment does not match working_dir" >&2
exit 1
fi
node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$environment_id" --no-cache --require-tenant
要求返回的环境 ID、HTTPS URL 和租户与所有者的信息匹配;上下文不完整或相互冲突时,返回 NEEDS_CONTEXT。绝不移除 --no-cache --require-tenant、更改配置,或切换身份验证方式/环境来恢复失败。每个请求都使用该精确的 <envUrl> 和 <tenantId>。
清单路径(双位置,均位于绑定的根目录内):
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
if [ -f .datamodel-manifest.json ]; then
printf '%s\n' '<working_dir>/.datamodel-manifest.json'
elif [ -f docs/plan-artifacts/.datamodel-manifest.json ]; then
printf '%s\n' '<working_dir>/docs/plan-artifacts/.datamodel-manifest.json'
else
echo "NEEDS_CONTEXT: no manifest in working_dir" >&2
exit 1
fi
从 <working_dir>/offline-profile.json 或所有者明确指定的 --profile-id 读取配置文件,并在此处内联应用 /edit-offline-profile 的解析规则。验证其与所有者的配置文件及所选环境中的实时配置文件匹配。快照/参数冲突、404 或权限失败,都不构成选择其他配置文件的理由;冲突时返回 NEEDS_CONTEXT,访问失败时返回 BLOCKED。不要仅为定位配置文件而执行编辑工作流。
如果不存在配置文件,则停止。建议:请先运行 /setup-offline-profile 以创建配置文件。
步骤 2 — 确定目标表
解析 $ARGUMENTS:
| 标志 | 效果 |
|---|---|
--table <logical-name> | 显式指定目标 |
--all-new | 仅在所有者确切允许的表范围内添加清单中缺少的表(批量模式);绝不扩展到无关的待处理表 |
| (无标志) | 交互模式:通过 AskUserQuestion 列出清单中允许但尚未加入所选配置文件的表(最多 4 个);如果候选项超过 4 个,则提示用户在下一条消息中按名称指定 |
显式指定的 --table 必须符合所有者允许的范围。选择冲突时返回 NEEDS_CONTEXT。对于批量 --all-new 模式,对每个获批的缺失表依次执行步骤 3–7;由于 Dataverse 配置文件项的 POST 请求会串行处理,因此必须依次执行。每次迭代都使用相同的根目录/环境/配置文件上下文。
步骤 3 — 前置条件检查(需要时自动启用)
查询目标表的 EntityMetadata:
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" GET \
"EntityDefinitions(LogicalName='<table>')?\$select=IsAvailableOffline,ChangeTrackingEnabled,OwnershipType,IsCustomizable" \
--tenant-id "<tenantId>"
| 标志状态 | 操作 |
|---|---|
两者均已为 true | 跳至步骤 4 |
任一为 false 且 IsCustomizable | 仅当当前离线审批涵盖这些确切的前置条件变更及发布时,才使用 update-entity-offline-flags.js;否则先展示变更内容并取得所有者/用户批准 |
IsCustomizable.Value = false | 停止并返回 BLOCKED: <table> 由系统管理,无法为离线使用设置标志。请使用其他表,或接受此行在离线模式下为只读。 |
获批后,仅针对此表执行辅助程序,并使用已解析的租户。
--tenant-id 同时绑定初始身份验证和 401 刷新;此项前置条件变更绝不能依赖环境中隐式设置的租户变量。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/update-entity-offline-flags.js" "<envUrl>" \
--table "<table>" --offline true --tracking true --tenant-id "<tenantId>"
检查返回的 HTTP 状态,而不只是进程退出状态。403/PrivilegeCheckFailed 属于 BLOCKED;不得更改身份或配置文件以绕过权限。
成功更改后,执行获批的前置条件发布:
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"PublishXml" --tenant-id "<tenantId>" --body '{
"ParameterXml": "<importexportxml><entities><entity><table></entity></entities></importexportxml>"
}'
仅发布此辅助程序更改的前置条件表。除非所有者另行批准发布所有待处理的环境自定义项,否则不得使用 PublishAllXml;仅针对表的批准不涵盖该回退方案。
仅提案模式下,不允许进行任何前置条件变更或发布。
步骤 4 — 范围选择器(单个问题)
从绑定的绝对清单中获取目标表的 lookups[],以提供默认值。在所选环境中执行绑定到根目录的行数探测,仅针对此表,以便进行参考数据分类。
使用 AskUserQuestion 提供 4 个选项,反映架构师的优先级级联:
| 选项标签 | 对应值 |
|---|---|
Organization rows — User's rows only (Recommended for most tables) | recorddistributioncriteria: 2,recordsownedbyme: true |
All records (for small reference data) | recorddistributioncriteria: 1 |
Related rows only (for pure child tables) | recorddistributioncriteria: 0 |
Custom — specify exact flags in next message | 提示用户输入文本 |
问题正文中的建议应根据架构师针对待添加表的启发式规则得出(使用 offline-profile-architect.md 步骤 4 中相同的优先级级联)。用户可以覆盖此建议。
在范围选项旁显示建议的关系、所选列和同步间隔。只有当用户提供的答案批准了这项确切的离线更改时,才可复用该答案;否则,须等用户作出选择后再执行步骤 5。取消操作会停止流程,且不会创建配置文件项。调用本身或编排标记都不能视为对这一关卡的答复。
步骤 5 — POST 项目和关联 + PATCH selectedcolumns
步骤 5a — POST 配置文件项
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"mobileofflineprofileitems" --tenant-id "<tenantId>" \
--body '{
"name": "<DisplayName>",
"[email protected]": "/mobileofflineprofiles(<profileId>)",
"selectedentitytypecode": "<table>",
"recorddistributioncriteria": <0|1|2>,
"recordsownedbyme": <bool>,
"recordsownedbymyteam": false,
"recordsownedbymybusinessunit": false,
"getrelatedentityrecords": true,
"syncintervalinminutes": 10
}' \
--include-headers
从 OData-EntityId 响应标头中获取 itemId。
步骤 5b — 为新表的关系 POST 关联
创建新项后,遍历新表的关系(来自清单的 lookups[] 和 EntityDefinitions(LogicalName='<table>')/ManyToOneRelationships 查询),并为目标已存在于配置文件中的每个关系 POST 一个 mobileofflineprofileitemassociation。
按照 shared/references/dataverse-offline-api.md §5–§6 中的流程:
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"mobileofflineprofileitemassociations" --tenant-id "<tenantId>" \
--body '{
"name": "<relationshipSchemaName>",
"relationshipdisplayname": "<relationshipSchemaName>",
"relationshipid": "<MetadataId-from-EntityDefinitions>",
"[email protected]": "/mobileofflineprofileitems(<newItemId>)"
}' \
--include-headers
关键:不要在请求正文中包含 selectedrelationshipsschema — 该字段由服务器填充(2026-05-24 的实测结果)。
对于 recorddistributioncriteria: 0(仅相关行) 的新表,必须至少包含一个入向关系,否则该表将同步零行。技能必须对此进行验证,并在仅同步相关行的表未创建任何关联时发出警告。
步骤 5c — PATCH selectedcolumns
按照 offline-profile-architect.md 步骤 6 中的确定性并集规则构建 selectedcolumns(始终包含的列 ∪ 查找列 ∪ 通过 screen-grep 找到的列,去重、排序)。将文件工具的 grep 操作范围限制在绝对路径 <working_dir>/app/ 和 <working_dir>/src/ 内,并限定为所选表;使用步骤 4 中获批的相同建议列。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" PATCH \
"mobileofflineprofileitems(<itemId>)" --tenant-id "<tenantId>" \
--body '{"selectedcolumns":"{\"Columns\":[...]}"}'
步骤 6 — 发布(定向 PublishXml)
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"PublishXml" --tenant-id "<tenantId>" --body '{
"ParameterXml": "<publish><mobileofflineprofiles><mobileofflineprofile><profileId></mobileofflineprofile></mobileofflineprofiles></publish>"
}'
仅发布此配置文件,不发布整个组织的自定义项。关于 0x80071141 循环关系处理和 PublishAllXml 回退,请参阅 shared/references/dataverse-offline-api.md §9。
所有关联的恢复命令、重新 GET 验证和重试都必须绑定到相同的根目录,并传入相同的环境/租户/配置文件/表/项上下文。
移除循环关联需要在该流程中取得用户的明确决定。组织范围的 PublishAllXml 回退需要获得对这一更广泛发布操作的批准;
不得擅自扩大仅针对配置文件的批准范围。不得在其他配置文件上重试,也不得绕过权限错误。确认发布完成后,再执行步骤 7。
如果关联的恢复流程要求使用其他离线辅助技能,应先将建议的更改交由所有者批准,然后再读取其绝对路径 ${PLUGIN_ROOT}/skills/<helper>/SKILL.md 下的文件。转交:
Arguments: --working-dir '<working_dir>' --profile-id "<profileId>" --table "<table>" <exact-approved-change-flags>
Context: MOBILE_APP_ORCHESTRATING=1, orchestrator, working_dir, phase, approved_scope,
selected environment ID/URL/tenant, profile ID, table allowlist, item ID,
supplied answers, --plan-only (if present)
接收方必须执行相同的规范根目录约定。缺少上下文时返回 NEEDS_CONTEXT;不得改为直接调用或重新发现。
步骤 7 — 更新制品
将新表的条目追加到 <working_dir>/offline-profile.json 的 tables[] 中。
该条目必须包含一个 schemaColumns 数组,其中列出此时绑定的绝对清单中该新增表的完整逻辑架构列名称集合。
这是架构协调基线,${PLUGIN_ROOT}/scripts/offline-profile-delta.js 会将未来的清单变更与此基线进行比较;省略该数组会导致报告 columnBaselineMissing,直到重新协调为止。它与 selectedColumns(经过筛选的同步集合)不同 — 参见
offline-profile-reconciliation.md。
参照
/setup-offline-profile Step 9a
中的规范条目格式,但不要执行该工作流。任何差异检查都要在单独的受控调用中使用
--project-root '<working_dir>'。
将以下内容追加到 <working_dir>/memory-bank.md 的 ## Offline profile 区块中:
addedTables:
- { at: 2026-05-19T..., table: <name>, itemId: <guid>, scope: <criterion> }
步骤 8 — 摘要
✓ Added <table> to profile.
Profile : <name>
New item ID : <guid>
Scope : <human-readable>
Sync : <minutes> min
Columns : <N> selected
Published : <ISO timestamp>
Total tables in profile now: <N>
Users who already have this profile assigned will receive the new table on their
next sign-in sync. To force-refresh, sign out and back in on the device.
状态码(最后一行)
DONE— 已添加表、已发布配置文件、已更新制品DONE_WITH_CONCERNS: <list>— 已添加,但存在注意事项(自动启用前置条件、发布超时后成功)NEEDS_CONTEXT: <missing>— 没有可供添加的配置文件,或没有目标表BLOCKED: <reason>— 目标上的 IsCustomizable=false、身份验证失败、POST/PATCH 失败
此技能不会执行的操作
- 将 N:N(多对多)关系添加到配置文件项中 — 这种情况并不常见,v0.1 的操作方案未涵盖(该方案假设通过
regardingobjectid查找字段实现 N:1 / 1:N)。如果用户有 M:N 用例,请引导他们使用 maker portal。 - 重新设计配置文件中的现有表 — 使用
/edit-offline-profile。 - 从配置文件中移除表 — 手动执行
DELETE /mobileofflineprofileitems(<itemId>)(目前没有对应技能)。