Skip to content

feat: inject PI_* env vars into Agent subprocesses (PI v2.5.0)#126

Merged
MistEO merged 3 commits intomainfrom
feat/PI_2.5
Mar 24, 2026
Merged

feat: inject PI_* env vars into Agent subprocesses (PI v2.5.0)#126
MistEO merged 3 commits intomainfrom
feat/PI_2.5

Conversation

@MistEO
Copy link
Copy Markdown
Owner

@MistEO MistEO commented Mar 24, 2026

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 命令层一路传递该配置直至进程创建。

新功能:

  • 基于项目上下文,为 Agent 子进程提供符合 PI v2.5.0 的 PI_* 环境变量(界面、客户端、语言、MaaFramework、控制器和资源元数据)。

增强:

  • 添加从前端到后端的管道,通过 maaService 将构造好的 PI_* 环境变量传递到 maa_start_tasks Tauri 命令以及 Agent 启动流程。
  • 引入一个共享工具,用于根据界面、控制器、资源、语言和 MaaFramework 版本上下文构建本地化的 PI_* 环境负载,并确保只有 PI_* 键被注入到 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:

  • Provide PI v2.5.0 PI_* environment variables (interface, client, language, MaaFramework, controller, and resource metadata) to Agent subprocesses based on project context.

Enhancements:

  • Add frontend-to-backend plumbing to pass constructed PI_* environment variables via maaService into the maa_start_tasks Tauri command and Agent startup path.
  • Introduce a shared utility for building localized PI_* environment payloads from interface, controller, resource, language, and MaaFramework version context and ensure only PI_* keys are injected into Agent processes.

新特性:

  • 为 Agent 子进程构建符合 PI v2.5.0 约定的 PI_* 环境变量(包括 interface、client、language、MaaFramework、controller 和 resource 元数据)。

增强内容:

  • PI_* 环境变量从 React 组件通过 maaService 传递到 Tauri 的 maa_start_tasks 命令,并在生成 Agent 子进程时应用这些环境变量。
  • 引入一个共享工具方法,用于基于项目的 interface、controller、resource 和 language 上下文构建本地化的 PI_* 环境变量负载。
Original summary in English

Summary by Sourcery

将符合 PI v2.5.0 标准的 PI_* 环境元数据注入到 Agent 子进程中,并从 React UI 通过 Tauri 命令层一路传递该配置直至进程创建。

新功能:

  • 基于项目上下文,为 Agent 子进程提供符合 PI v2.5.0 的 PI_* 环境变量(界面、客户端、语言、MaaFramework、控制器和资源元数据)。

增强:

  • 添加从前端到后端的管道,通过 maaService 将构造好的 PI_* 环境变量传递到 maa_start_tasks Tauri 命令以及 Agent 启动流程。
  • 引入一个共享工具,用于根据界面、控制器、资源、语言和 MaaFramework 版本上下文构建本地化的 PI_* 环境负载,并确保只有 PI_* 键被注入到 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:

  • Provide PI v2.5.0 PI_* environment variables (interface, client, language, MaaFramework, controller, and resource metadata) to Agent subprocesses based on project context.

Enhancements:

  • Add frontend-to-backend plumbing to pass constructed PI_* environment variables via maaService into the maa_start_tasks Tauri command and Agent startup path.
  • Introduce a shared utility for building localized PI_* environment payloads from interface, controller, resource, language, and MaaFramework version context and ensure only PI_* keys are injected into Agent processes.

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
Copilot AI review requested due to automatic review settings March 24, 2026 08:59
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Sourcery 对开源项目免费 —— 如果你觉得我们的 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的 Review。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the PI_* env var map from current UI/PI context.
  • Plumbed piEnvs from UI start flows → maaService.startTasks → Tauri maa_start_tasks command.
  • Injected the provided env vars into the spawned Agent Command on 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.

@MistEO MistEO requested a review from Copilot March 24, 2026 12:14
@MistEO
Copy link
Copy Markdown
Owner Author

MistEO commented Mar 24, 2026

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嗨,我在这里给出了一些总体反馈:

  • 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.

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English

Hey - I've left some high level feedback:

  • 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.
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.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

}

const key = text.slice(1);
return translations?.[key] || key;
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
return translations?.[key] || key;
return translations?.[key] ?? key;

Copilot uses AI. Check for mistakes.
Comment on lines +390 to +397
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,
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@MistEO MistEO merged commit 786ec26 into main Mar 24, 2026
3 checks passed
@MistEO MistEO deleted the feat/PI_2.5 branch March 24, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants