feat: inject PI_* env vars into Agent subprocesses (PI v2.5.0)#126
feat: inject PI_* env vars into Agent subprocesses (PI v2.5.0)#126
Conversation
Implement the PI v2.5.0 convention from MaaFramework#1226: when launching Agent child processes, MXU now injects PI_INTERFACE_VERSION, PI_CLIENT_NAME, PI_CLIENT_VERSION, PI_CLIENT_LANGUAGE, PI_CLIENT_MAAFW_VERSION, PI_VERSION, PI_CONTROLLER, and PI_RESOURCE environment variables with i18n-resolved values. Made-with: Cursor
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:
resolveI18nInObject的 JSDoc 中说明它只解析以$开头的 i18n 字符串,但当前实现会解析所有字符串;建议将逻辑与文档中关于$前缀的说明对齐,以避免无意中重写非 i18n 字段(例如 ID 或键)。- 在
maaService.startTasks中,只有在hasAgent为 true 时才会转发piEnvs;如果你的意图是只要计算出了piEnvs,后端就应该始终收到一个值(即使将来在无 agent 的场景下使用),可以考虑将piEnvs参数与hasAgent解耦。
给 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- The JSDoc for `resolveI18nInObject` says it only resolves `$`-prefixed i18n strings, but the implementation currently resolves all string values; consider aligning the logic with the documented `$` prefix behavior to avoid unintentionally rewriting non-i18n fields (e.g., IDs or keys).
- In `maaService.startTasks`, `piEnvs` is only forwarded when `hasAgent` is true; if the intent is that the backend always receives a value when `piEnvs` is computed, even for future non-agent use, you may want to decouple the `piEnvs` parameter from `hasAgent`.
## Individual Comments
### Comment 1
<location path="src/utils/piEnv.ts" line_range="18-41" />
<code_context>
+ * 递归解析对象中所有 `$` 开头的 i18n 字符串字段。
+ * 返回的新对象中 label、description 等字段已替换为最终展示文本。
+ */
+function resolveI18nInObject(
+ obj: Record<string, unknown>,
+ translations?: Record<string, string>,
+): Record<string, unknown> {
+ const resolved: Record<string, unknown> = {};
+ for (const [key, value] of Object.entries(obj)) {
+ if (typeof value === 'string') {
+ resolved[key] = resolveI18nText(value, translations);
+ } else if (Array.isArray(value)) {
+ resolved[key] = value.map((item) =>
+ typeof item === 'object' && item !== null
+ ? resolveI18nInObject(item as Record<string, unknown>, translations)
+ : typeof item === 'string'
+ ? resolveI18nText(item, translations)
+ : item,
+ );
+ } else if (typeof value === 'object' && value !== null) {
+ resolved[key] = resolveI18nInObject(value as Record<string, unknown>, translations);
+ } else {
+ resolved[key] = value;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 考虑仅对真正作为 i18n key 的字段做解析,以避免产生意外的转换。
目前每个字符串(包括嵌套的)都会传入 resolveI18nText。如果 resolveI18nText 会修改非 key 字符串,就有可能更改诸如 ID 或 URL 之类的值,同时也会产生额外的开销。如果你有比如以 "$" 作为前缀的 key 这种约定,可以在调用 resolveI18nText 之前先做判断,只对真正的 i18n key 进行处理。
```suggestion
function resolveI18nInObject(
obj: Record<string, unknown>,
translations?: Record<string, string>,
): Record<string, unknown> {
const resolved: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
// 仅对以 `$` 开头的字符串视为 i18n key 进行解析,避免误处理普通字符串(如 ID、URL 等)
resolved[key] = value.startsWith('$') ? resolveI18nText(value, translations) : value;
} else if (Array.isArray(value)) {
resolved[key] = value.map((item) =>
typeof item === 'object' && item !== null
? resolveI18nInObject(item as Record<string, unknown>, translations)
: typeof item === 'string'
? item.startsWith('$')
? resolveI18nText(item, translations)
: item
: item,
);
} else if (typeof value === 'object' && value !== null) {
resolved[key] = resolveI18nInObject(value as Record<string, unknown>, translations);
} else {
resolved[key] = value;
}
}
return resolved;
}
```
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的 Review。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- The JSDoc for
resolveI18nInObjectsays it only resolves$-prefixed i18n strings, but the implementation currently resolves all string values; consider aligning the logic with the documented$prefix behavior to avoid unintentionally rewriting non-i18n fields (e.g., IDs or keys). - In
maaService.startTasks,piEnvsis only forwarded whenhasAgentis true; if the intent is that the backend always receives a value whenpiEnvsis computed, even for future non-agent use, you may want to decouple thepiEnvsparameter fromhasAgent.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The JSDoc for `resolveI18nInObject` says it only resolves `$`-prefixed i18n strings, but the implementation currently resolves all string values; consider aligning the logic with the documented `$` prefix behavior to avoid unintentionally rewriting non-i18n fields (e.g., IDs or keys).
- In `maaService.startTasks`, `piEnvs` is only forwarded when `hasAgent` is true; if the intent is that the backend always receives a value when `piEnvs` is computed, even for future non-agent use, you may want to decouple the `piEnvs` parameter from `hasAgent`.
## Individual Comments
### Comment 1
<location path="src/utils/piEnv.ts" line_range="18-41" />
<code_context>
+ * 递归解析对象中所有 `$` 开头的 i18n 字符串字段。
+ * 返回的新对象中 label、description 等字段已替换为最终展示文本。
+ */
+function resolveI18nInObject(
+ obj: Record<string, unknown>,
+ translations?: Record<string, string>,
+): Record<string, unknown> {
+ const resolved: Record<string, unknown> = {};
+ for (const [key, value] of Object.entries(obj)) {
+ if (typeof value === 'string') {
+ resolved[key] = resolveI18nText(value, translations);
+ } else if (Array.isArray(value)) {
+ resolved[key] = value.map((item) =>
+ typeof item === 'object' && item !== null
+ ? resolveI18nInObject(item as Record<string, unknown>, translations)
+ : typeof item === 'string'
+ ? resolveI18nText(item, translations)
+ : item,
+ );
+ } else if (typeof value === 'object' && value !== null) {
+ resolved[key] = resolveI18nInObject(value as Record<string, unknown>, translations);
+ } else {
+ resolved[key] = value;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider limiting i18n resolution to fields that are actually i18n keys to avoid unintended transformations.
Right now every string (including nested ones) is passed to resolveI18nText. If resolveI18nText can modify non-key strings, this risks changing values like IDs or URLs, and also does extra work. If you have a convention such as "$"-prefixed keys, consider checking that before calling resolveI18nText so only true i18n keys are processed.
```suggestion
function resolveI18nInObject(
obj: Record<string, unknown>,
translations?: Record<string, string>,
): Record<string, unknown> {
const resolved: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
// 仅对以 `$` 开头的字符串视为 i18n key 进行解析,避免误处理普通字符串(如 ID、URL 等)
resolved[key] = value.startsWith('$') ? resolveI18nText(value, translations) : value;
} else if (Array.isArray(value)) {
resolved[key] = value.map((item) =>
typeof item === 'object' && item !== null
? resolveI18nInObject(item as Record<string, unknown>, translations)
: typeof item === 'string'
? item.startsWith('$')
? resolveI18nText(item, translations)
: item
: item,
);
} else if (typeof value === 'object' && value !== null) {
resolved[key] = resolveI18nInObject(value as Record<string, unknown>, translations);
} else {
resolved[key] = value;
}
}
return resolved;
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
This PR implements the ProjectInterface v2.5.0 convention to inject a set of PI_* environment variables into MaaFramework Agent child processes, so Agents can receive client/PI metadata (with i18n-resolved controller/resource payloads) at startup.
Changes:
- Added
buildPiEnvVars(with deep i18n resolution) to construct thePI_*env var map from current UI/PI context. - Plumbed
piEnvsfrom UI start flows →maaService.startTasks→ Taurimaa_start_taskscommand. - Injected the provided env vars into the spawned Agent
Commandon the Rust side.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/piEnv.ts | New helper to build PI v2.5.0 PI_* env vars and i18n-resolve controller/resource JSON payloads. |
| src/utils/index.ts | Re-exported piEnv from the general utils barrel. |
| src/services/maaService.ts | Extended startTasks to accept and invoke backend with optional piEnvs. |
| src/components/Toolbar.tsx | Builds and passes piEnvs when starting tasks from the toolbar flow. |
| src/components/DashboardView.tsx | Builds and passes piEnvs when starting tasks from the dashboard flow. |
| src-tauri/src/commands/maa_agent.rs | Accepts optional pi_envs and injects them into Agent subprocess environment. |
|
@sourcery-ai review |
There was a problem hiding this comment.
嗨,我在这里给出了一些总体反馈:
- 在
resolvePiI18nText中,使用translations?.[key] || key会把空的翻译字符串当作缺失处理;建议显式检查key in translations(或translations?.[key] !== undefined),这样可以保留合法的空翻译。 - 在
maaService.startTasks中,piEnvs是从调用方传入的,当hasAgent为 false 时才会被置为 null;为了避免未来的调用点不小心遗漏 PI_* 元数据,建议将buildPiEnvVars的调用集中放在startTasks内(基于 store 状态)完成,而不是要求每个 UI 组件自己构造并传入。
给 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- In `resolvePiI18nText`, using `translations?.[key] || key` will treat an empty translated string as missing; consider explicitly checking `key in translations` (or `translations?.[key] !== undefined`) so valid empty translations are preserved.
- In `maaService.startTasks`, `piEnvs` is passed through from callers and then conditionally nulled when `hasAgent` is false; to avoid future call sites accidentally omitting PI_* metadata, consider centralizing the `buildPiEnvVars` call inside `startTasks` (based on store state) instead of requiring each UI component to construct and forward it.帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've left some high level feedback:
- In
resolvePiI18nText, usingtranslations?.[key] || keywill treat an empty translated string as missing; consider explicitly checkingkey in translations(ortranslations?.[key] !== undefined) so valid empty translations are preserved. - In
maaService.startTasks,piEnvsis passed through from callers and then conditionally nulled whenhasAgentis false; to avoid future call sites accidentally omitting PI_* metadata, consider centralizing thebuildPiEnvVarscall insidestartTasks(based on store state) instead of requiring each UI component to construct and forward it.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `resolvePiI18nText`, using `translations?.[key] || key` will treat an empty translated string as missing; consider explicitly checking `key in translations` (or `translations?.[key] !== undefined`) so valid empty translations are preserved.
- In `maaService.startTasks`, `piEnvs` is passed through from callers and then conditionally nulled when `hasAgent` is false; to avoid future call sites accidentally omitting PI_* metadata, consider centralizing the `buildPiEnvVars` call inside `startTasks` (based on store state) instead of requiring each UI component to construct and forward it.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
src/utils/piEnv.ts
Outdated
| } | ||
|
|
||
| const key = text.slice(1); | ||
| return translations?.[key] || key; |
There was a problem hiding this comment.
resolvePiI18nText uses translations?.[key] || key, which treats an intentionally-empty translation ("") as missing and falls back to the key. If empty strings are valid translations, use nullish coalescing (or an explicit in check) so only undefined/missing entries fall back.
| return translations?.[key] || key; | |
| return translations?.[key] ?? key; |
src/services/maaService.ts
Outdated
| const hasAgent = agentConfigs && agentConfigs.length > 0; | ||
| const taskIds = await invoke<number[]>('maa_start_tasks', { | ||
| instanceId, | ||
| tasks, | ||
| agentConfigs: agentConfigs && agentConfigs.length > 0 ? agentConfigs : null, | ||
| agentConfigs: hasAgent ? agentConfigs : null, | ||
| cwd: cwd || '.', | ||
| tcpCompatMode: tcpCompatMode || false, | ||
| piEnvs: hasAgent && piEnvs ? piEnvs : null, |
There was a problem hiding this comment.
const hasAgent = agentConfigs && agentConfigs.length > 0; can evaluate to undefined (not a boolean) when agentConfigs is missing. Consider coercing to a boolean (e.g., via optional chaining on length) to keep the intent clear and avoid subtle type/logic issues when refactoring.
Implement the PI v2.5.0 convention from MaaFramework#1226: when launching Agent child processes, MXU now injects PI_INTERFACE_VERSION, PI_CLIENT_NAME, PI_CLIENT_VERSION, PI_CLIENT_LANGUAGE, PI_CLIENT_MAAFW_VERSION, PI_VERSION, PI_CONTROLLER, and PI_RESOURCE environment variables with i18n-resolved values.
Made-with: Cursor
Summary by Sourcery
将符合 PI v2.5.0 标准的 PI_* 环境元数据注入到 Agent 子进程中,并从 React UI 通过 Tauri 命令层一路传递该配置直至进程创建。
新功能:
增强:
maaService将构造好的 PI_* 环境变量传递到maa_start_tasksTauri 命令以及 Agent 启动流程。Original summary in English
Summary by Sourcery
Inject PI v2.5.0-compliant PI_* environment metadata into Agent subprocesses and thread this configuration from the React UI through the Tauri command layer to process spawning.
New Features:
Enhancements:
新特性:
PI_*环境变量(包括 interface、client、language、MaaFramework、controller 和 resource 元数据)。增强内容:
PI_*环境变量从 React 组件通过maaService传递到 Tauri 的maa_start_tasks命令,并在生成 Agent 子进程时应用这些环境变量。PI_*环境变量负载。Original summary in English
Summary by Sourcery
将符合 PI v2.5.0 标准的 PI_* 环境元数据注入到 Agent 子进程中,并从 React UI 通过 Tauri 命令层一路传递该配置直至进程创建。
新功能:
增强:
maaService将构造好的 PI_* 环境变量传递到maa_start_tasksTauri 命令以及 Agent 启动流程。Original summary in English
Summary by Sourcery
Inject PI v2.5.0-compliant PI_* environment metadata into Agent subprocesses and thread this configuration from the React UI through the Tauri command layer to process spawning.
New Features:
Enhancements: