@larktask/aamp-feishu-task-agent 0.1.0-dev.174 → 0.1.1-dev.3

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.
@@ -5,8 +5,12 @@ umask 077
5
5
  AGENT=""
6
6
  APP_ID=""
7
7
  APP_SECRET=""
8
+ APP_TENANT_BRAND="feishu"
8
9
  BOT_NAME=""
9
10
  LARK_CLI_PROFILE=""
11
+ AGENT_EXECUTION_LOCATION=""
12
+ AGENT_ATTACHMENT_POLICY=""
13
+ AGENT_TASK_DISPATCH_CONCURRENCY=""
10
14
  AAMP_HOST="https://meshmail.ai"
11
15
  DEBUG_MODE="false"
12
16
  AAMP_TASK_START_MODE="install"
@@ -15,6 +19,7 @@ AAMP_TASK_ENTRY="${AAMP_TASK_ENTRY:-}"
15
19
  AAMP_TASK_INTERNAL="${AAMP_TASK_INTERNAL:-false}"
16
20
  AAMP_TASK_INTERNAL_RESULT_FD="${AAMP_TASK_INTERNAL_RESULT_FD:-3}"
17
21
  AAMP_TASK_INTERNAL_INPUT_FD="${AAMP_TASK_INTERNAL_INPUT_FD:-4}"
22
+ AAMP_TASK_INTERNAL_EXECUTION_LOCATION="${AAMP_TASK_INTERNAL_EXECUTION_LOCATION:-}"
18
23
  NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org/}"
19
24
  NPM_CACHE_DIR="${NPM_CONFIG_CACHE:-${npm_config_cache:-${TMPDIR:-/tmp}/aamp-one-click-npm-cache}}"
20
25
  NPM_GLOBAL_PREFIX="${NPM_GLOBAL_PREFIX:-$HOME/.aamp/npm-global}"
@@ -51,22 +56,63 @@ CODEX_CHATGPT_APP_CLI="/Applications/ChatGPT.app/Contents/Resources/codex"
51
56
  CODEX_APP_CLI="/Applications/Codex.app/Contents/Resources/codex"
52
57
  CODEX_AUTO_UPDATE="${CODEX_AUTO_UPDATE:-true}"
53
58
  CODEX_NPM_PACKAGE="${CODEX_NPM_PACKAGE:-@openai/codex}"
59
+ CODEX_UPDATE_CACHE_FILE="${CODEX_UPDATE_CACHE_FILE:-$HOME/.aamp/feishu-task-agent/codex-update-cache.json}"
60
+ CODEX_UPDATE_CACHE_TTL_SECONDS="${CODEX_UPDATE_CACHE_TTL_SECONDS:-86400}"
54
61
  CODEX_ACP_PKG="${CODEX_ACP_PKG:-@agentclientprotocol/codex-acp@1.0.2}"
62
+ AAMP_TRAE_CLI_BIN="${AAMP_TRAE_CLI_BIN:-}"
63
+ AAMP_TRAE_LOGIN_STATUS_TIMEOUT_SECONDS="${AAMP_TRAE_LOGIN_STATUS_TIMEOUT_SECONDS:-10}"
64
+ AAMP_TRAECODE_CLI_BIN="${AAMP_TRAECODE_CLI_BIN:-}"
65
+ AAMP_TRAECODE_CHECK_TIMEOUT_SECONDS="${AAMP_TRAECODE_CHECK_TIMEOUT_SECONDS:-10}"
66
+ AAMP_TRAECODE_READINESS_HELPER="${AAMP_TRAECODE_READINESS_HELPER:-}"
67
+ AAMP_TRAECODE_UPDATE_TTY="${AAMP_TRAECODE_UPDATE_TTY:-/dev/tty}"
68
+ TRAEX_INSTALLER_URL="${TRAEX_INSTALLER_URL:-https://code.byted.org/api/tos-proxy/download/traex_install.sh}"
69
+ WORKBUDDY_APP_CLI="/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy"
70
+ WORKBUDDY_AI_APP_CLI="/Applications/WorkBuddy AI.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy"
55
71
  LARK_REGISTER_APP_SDK="${LARK_REGISTER_APP_SDK:-@larksuiteoapi/node-sdk@1.68.0}"
56
72
  LARK_CLI_MIN_VERSION="${LARK_CLI_MIN_VERSION:-1.0.64}"
57
- FEISHU_APP_SCOPES_TENANT="${FEISHU_APP_SCOPES_TENANT:-im:message,im:message:send_as_bot,im:message:readonly,im:resource,cardkit:card:write,task:task,task:comment,task:task:readonly,task:comment:readonly,task:attachment:delete,task:attachment:file:download,task:attachment:read,task:attachment:upload,task:attachment:write,task:comment:delete,task:comment:read,task:comment:write,task:comment:writeonly,task:task:delete,task:task:read,task:task:write,task:task:writeonly,task:tasklist:delete,task:tasklist:read,task:tasklist:write,task:tasklist:writeonly,search:docs:read,base:app:copy,base:app:create,base:app:read,base:app:update,base:block:create,base:block:delete,base:block:read,base:block:update,base:dashboard:create,base:dashboard:delete,base:dashboard:read,base:dashboard:update,base:field:create,base:field:delete,base:field:read,base:field:update,base:form:create,base:form:delete,base:form:read,base:form:update,base:history:read,base:record:create,base:record:delete,base:record:read,base:record:update,base:role:create,base:role:delete,base:role:read,base:role:update,base:table:create,base:table:delete,base:table:read,base:table:update,base:view:read,base:view:write_only,base:workflow:create,base:workflow:read,base:workflow:update,board:whiteboard:node:create,board:whiteboard:node:read,calendar:calendar.event:create,calendar:calendar.event:delete,calendar:calendar.event:read,calendar:calendar.event:reply,calendar:calendar.event:update,calendar:calendar.free_busy:read,calendar:calendar:create,calendar:calendar:delete,calendar:calendar:read,calendar:calendar:update,contact:user.base:readonly,contact:user.basic_profile:readonly,docs:document.media:download,docs:document.media:upload,docs:document:export,docs:document:import,docx:document:create,docx:document:readonly,docx:document:write_only,drive:drive.metadata:readonly,drive:file:download,drive:file:upload,im:chat.managers:write_only,im:chat.members:read,im:chat.members:write_only,im:chat.moderation:read,im:chat:moderation:write_only,im:message.pins:read,im:message.pins:write_only,im:message.reactions:read,im:message.reactions:write_only,im:message:recall,mail:user_mailbox.event.mail_address:read,mail:user_mailbox.mail_contact:read,mail:user_mailbox.message.address:read,mail:user_mailbox.message.body:read,mail:user_mailbox.message.subject:read,mindnote:node:create,mindnote:node:read,minutes:minutes.basic:read,minutes:minutes.media:export,minutes:minutes:readonly,sheets:spreadsheet.meta:read,sheets:spreadsheet.meta:write_only,sheets:spreadsheet:create,sheets:spreadsheet:read,sheets:spreadsheet:write_only,slides:presentation:create,slides:presentation:read,slides:presentation:update,slides:presentation:write_only,task:custom_field:read,task:custom_field:write,task:section:read,task:section:write,vc:meeting.bot.join:write,vc:meeting.meetingevent:read,vc:meeting.message:write,vc:record:readonly,wiki:member:create,wiki:member:retrieve,wiki:member:update,wiki:node:copy,wiki:node:create,wiki:node:move,wiki:node:read,wiki:node:retrieve,wiki:space:read,wiki:space:retrieve,wiki:space:write_only}"
58
- FEISHU_APP_SCOPES_USER="${FEISHU_APP_SCOPES_USER:-im:message,im:message:readonly,im:resource,cardkit:card:write,task:task,task:comment,task:task:readonly,task:comment:readonly,task:attachment:delete,task:attachment:file:download,task:attachment:read,task:attachment:upload,task:attachment:write,task:comment:delete,task:comment:read,task:comment:write,task:comment:writeonly,task:task:delete,task:task:read,task:task:write,task:task:writeonly,task:tasklist:delete,task:tasklist:read,task:tasklist:write,task:tasklist:writeonly,search:docs:read,search:message,base:app:copy,base:app:create,base:app:read,base:app:update,base:block:create,base:block:delete,base:block:read,base:block:update,base:dashboard:create,base:dashboard:delete,base:dashboard:read,base:dashboard:update,base:field:create,base:field:delete,base:field:read,base:field:update,base:form:create,base:form:delete,base:form:read,base:form:update,base:history:read,base:record:create,base:record:delete,base:record:read,base:record:update,base:role:create,base:role:delete,base:role:read,base:role:update,base:table:create,base:table:delete,base:table:read,base:table:update,base:view:read,base:view:write_only,base:workflow:create,base:workflow:read,base:workflow:update,board:whiteboard:node:create,board:whiteboard:node:read,calendar:calendar.event:create,calendar:calendar.event:delete,calendar:calendar.event:read,calendar:calendar.event:reply,calendar:calendar.event:update,calendar:calendar.free_busy:read,calendar:calendar:create,calendar:calendar:delete,calendar:calendar:read,calendar:calendar:update,contact:user.base:readonly,contact:user.basic_profile:readonly,contact:user:search,docs:document.media:download,docs:document.media:upload,docs:document:export,docs:document:import,docx:document:create,docx:document:readonly,docx:document:write_only,drive:drive.metadata:readonly,drive:file:download,drive:file:upload,im:chat.managers:write_only,im:chat.members:read,im:chat.members:write_only,im:chat.moderation:read,im:chat.nickname:read,im:chat.nickname:write,im:chat.user_setting:read,im:chat.user_setting:write,im:chat:read,im:chat:update,im:chat:create_by_user,im:chat:moderation:write_only,im:feed.flag:read,im:feed.flag:write,im:feed.shortcut:read,im:feed.shortcut:write,im:feed_group_v1:read,im:feed_group_v1:write,im:message.group_msg:get_as_user,im:message.p2p_msg:get_as_user,im:message.pins:read,im:message.pins:write_only,im:message.reactions:read,im:message.reactions:write_only,im:message:recall,mail:event,mail:user_mailbox.event.mail_address:read,mail:user_mailbox.mail_contact:read,mail:user_mailbox.message.address:read,mail:user_mailbox.message.body:read,mail:user_mailbox.message.subject:read,mindnote:node:create,mindnote:node:read,minutes:minutes.artifacts:read,minutes:minutes.basic:read,minutes:minutes.media:export,minutes:minutes.search:read,minutes:minutes.upload:write,minutes:minutes:readonly,minutes:minutes:update,profile:user_profile:read,sheets:spreadsheet.meta:read,sheets:spreadsheet.meta:write_only,sheets:spreadsheet:create,sheets:spreadsheet:read,sheets:spreadsheet:write_only,slides:presentation:create,slides:presentation:read,slides:presentation:update,slides:presentation:write_only,task:custom_field:read,task:custom_field:write,task:section:read,task:section:write,vc:meeting.bot.join:write,vc:meeting.meetingevent:read,vc:meeting.message:write,vc:meeting.search:read,vc:note:read,vc:record:readonly,wiki:member:create,wiki:member:retrieve,wiki:member:update,wiki:node:copy,wiki:node:create,wiki:node:move,wiki:node:read,wiki:node:retrieve,wiki:space:read,wiki:space:retrieve,wiki:space:write_only}"
73
+ AAMP_FEISHU_APP_SCOPES_TENANT_OVERRIDE="${FEISHU_APP_SCOPES_TENANT-}"
74
+ AAMP_FEISHU_APP_SCOPES_USER_OVERRIDE="${FEISHU_APP_SCOPES_USER-}"
75
+ AAMP_FEISHU_USER_AUTH_REQUIRED_SCOPES_OVERRIDE="${FEISHU_USER_AUTH_REQUIRED_SCOPES-}"
76
+ AAMP_FEISHU_USER_AUTH_REQUESTED_SCOPES_OVERRIDE="${FEISHU_USER_AUTH_REQUESTED_SCOPES-}"
77
+ AAMP_FEISHU_USER_AUTH_EXCLUDES_OVERRIDE="${FEISHU_USER_AUTH_EXCLUDES-}"
78
+ FEISHU_APP_SCOPES_TENANT="$AAMP_FEISHU_APP_SCOPES_TENANT_OVERRIDE"
79
+ FEISHU_APP_SCOPES_USER="$AAMP_FEISHU_APP_SCOPES_USER_OVERRIDE"
59
80
  FEISHU_APP_EVENTS_TENANT="${FEISHU_APP_EVENTS_TENANT:-task.task.update_user_access_v2}"
60
81
  FEISHU_APP_EVENTS_USER="${FEISHU_APP_EVENTS_USER:-task.task.update_user_access_v2}"
61
- FEISHU_USER_AUTH_DOMAINS="${FEISHU_USER_AUTH_DOMAINS:-base,calendar,contact,docs,im,mail,mindnotes,minutes,note,sheets,slides,task,vc,wiki}"
62
- FEISHU_USER_AUTH_EXCLUDES="${FEISHU_USER_AUTH_EXCLUDES:-im:message.send_as_user,mail:user_mailbox.message:send,mail:user_mailbox.rule:read,mail:user_mailbox.folder:write,mail:user_mailbox.rule:write,mail:user_mailbox.message:modify,mail:user_mailbox.message:readonly,mail:user_mailbox.folder:read,mail:user_mailbox.mail_contact:write,mail:user_mailbox:readonly}"
63
- FEISHU_USER_AUTH_REQUIRED_SCOPES="${FEISHU_USER_AUTH_REQUIRED_SCOPES:-im:message im:message:readonly im:resource cardkit:card:write task:task task:comment task:task:readonly task:comment:readonly task:attachment:delete task:attachment:file:download task:attachment:read task:attachment:upload task:attachment:write task:comment:delete task:comment:read task:comment:write task:comment:writeonly task:task:delete task:task:read task:task:write task:task:writeonly task:tasklist:delete task:tasklist:read task:tasklist:write task:tasklist:writeonly search:docs:read search:message base:app:copy base:app:create base:app:read base:app:update base:block:create base:block:delete base:block:read base:block:update base:dashboard:create base:dashboard:delete base:dashboard:read base:dashboard:update base:field:create base:field:delete base:field:read base:field:update base:form:create base:form:delete base:form:read base:form:update base:history:read base:record:create base:record:delete base:record:read base:record:update base:role:create base:role:delete base:role:read base:role:update base:table:create base:table:delete base:table:read base:table:update base:view:read base:view:write_only base:workflow:create base:workflow:read base:workflow:update board:whiteboard:node:create board:whiteboard:node:read calendar:calendar.event:create calendar:calendar.event:delete calendar:calendar.event:read calendar:calendar.event:reply calendar:calendar.event:update calendar:calendar.free_busy:read calendar:calendar:create calendar:calendar:delete calendar:calendar:read calendar:calendar:update contact:user.base:readonly contact:user.basic_profile:readonly contact:user:search docs:document.media:download docs:document.media:upload docs:document:export docs:document:import docx:document:create docx:document:readonly docx:document:write_only drive:drive.metadata:readonly drive:file:download drive:file:upload im:chat.managers:write_only im:chat.members:read im:chat.members:write_only im:chat.moderation:read im:chat.nickname:read im:chat.nickname:write im:chat.user_setting:read im:chat.user_setting:write im:chat:read im:chat:update im:chat:create_by_user im:chat:moderation:write_only im:feed.flag:read im:feed.flag:write im:feed.shortcut:read im:feed.shortcut:write im:feed_group_v1:read im:feed_group_v1:write im:message.group_msg:get_as_user im:message.p2p_msg:get_as_user im:message.pins:read im:message.pins:write_only im:message.reactions:read im:message.reactions:write_only im:message:recall mail:event mail:user_mailbox.event.mail_address:read mail:user_mailbox.mail_contact:read mail:user_mailbox.message.address:read mail:user_mailbox.message.body:read mail:user_mailbox.message.subject:read mindnote:node:create mindnote:node:read minutes:minutes.artifacts:read minutes:minutes.basic:read minutes:minutes.media:export minutes:minutes.search:read minutes:minutes.upload:write minutes:minutes:readonly minutes:minutes:update profile:user_profile:read sheets:spreadsheet.meta:read sheets:spreadsheet.meta:write_only sheets:spreadsheet:create sheets:spreadsheet:read sheets:spreadsheet:write_only slides:presentation:create slides:presentation:read slides:presentation:update slides:presentation:write_only task:custom_field:read task:custom_field:write task:section:read task:section:write vc:meeting.bot.join:write vc:meeting.meetingevent:read vc:meeting.message:write vc:meeting.search:read vc:note:read vc:record:readonly wiki:member:create wiki:member:retrieve wiki:member:update wiki:node:copy wiki:node:create wiki:node:move wiki:node:read wiki:node:retrieve wiki:space:read wiki:space:retrieve wiki:space:write_only}"
64
- ACP_BRIDGE_PKG="${ACP_BRIDGE_PKG:-@zengxingyuan/aamp-acp-bridge@0.1.28-dev.20}"
82
+ FEISHU_USER_AUTH_EXCLUDES="$AAMP_FEISHU_USER_AUTH_EXCLUDES_OVERRIDE"
83
+ FEISHU_USER_AUTH_REQUIRED_SCOPES="$AAMP_FEISHU_USER_AUTH_REQUIRED_SCOPES_OVERRIDE"
84
+ FEISHU_USER_AUTH_REQUESTED_SCOPES="$AAMP_FEISHU_USER_AUTH_REQUESTED_SCOPES_OVERRIDE"
85
+ FEISHU_USER_AUTH_CORE_SCOPES="${FEISHU_USER_AUTH_CORE_SCOPES:-}"
86
+ FEISHU_USER_AUTH_OPTIONAL_SCOPES="${FEISHU_USER_AUTH_OPTIONAL_SCOPES:-}"
87
+ FEISHU_USER_AUTH_MODE="${FEISHU_USER_AUTH_MODE:-optional}"
88
+ FEISHU_TASK_PROFILE_DOMAINS="${FEISHU_TASK_PROFILE_DOMAINS:-task}"
89
+ AAMP_FEISHU_AUTH_STATE_DIR="${AAMP_FEISHU_AUTH_STATE_DIR:-$HOME/.aamp/feishu-bridge/auth-capabilities}"
90
+ AAMP_TASK_ALLOW_PACKAGE_OVERRIDES="${AAMP_TASK_ALLOW_PACKAGE_OVERRIDES:-false}"
91
+ if [ "$AAMP_TASK_INTERNAL" = "true" ] \
92
+ && [ "${AAMP_TASK_PACKAGE_OVERRIDES_RESOLVED:-false}" = "true" ]; then
93
+ AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG="${AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG:-}"
94
+ AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG="${AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG:-}"
95
+ AAMP_TASK_REQUESTED_AIME_ACP_PKG="${AAMP_TASK_REQUESTED_AIME_ACP_PKG:-}"
96
+ else
97
+ AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG="${ACP_BRIDGE_PKG:-${AAMP_TASK_ACP_BRIDGE_PKG:-}}"
98
+ AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG="${FEISHU_BRIDGE_PKG:-${AAMP_TASK_FEISHU_BRIDGE_PKG:-}}"
99
+ AAMP_TASK_REQUESTED_AIME_ACP_PKG="${AIME_ACP_PKG:-${AAMP_TASK_AIME_ACP_PKG:-}}"
100
+ fi
101
+ unset AAMP_TASK_PACKAGE_OVERRIDES_RESOLVED
102
+ unset ACP_BRIDGE_PKG AAMP_TASK_ACP_BRIDGE_PKG
103
+ unset FEISHU_BRIDGE_PKG AAMP_TASK_FEISHU_BRIDGE_PKG
104
+ unset AIME_ACP_PKG AIME_ACP_REGISTRY AAMP_TASK_AIME_ACP_PKG
105
+ ACP_BRIDGE_PKG="${ACP_BRIDGE_PKG:-@luckyterry/aamp-acp-bridge@0.1.29-dev.0}"
106
+ AIME_ACP_PKG="${AIME_ACP_PKG:-@tengchengwei/aime-acp@0.1.1-dev.1}"
107
+ AIME_ACP_REGISTRY="${AIME_ACP_REGISTRY:-https://bnpm.byted.org}"
65
108
  CLI_BRIDGE_PKG="${CLI_BRIDGE_PKG:-@zengxingyuan/aamp-cli-bridge@0.1.7-dev.14}"
66
- FEISHU_BRIDGE_PKG="${FEISHU_BRIDGE_PKG:-@zengxingyuan/aamp-feishu-bridge@0.1.51}"
109
+ FEISHU_BRIDGE_PKG="${FEISHU_BRIDGE_PKG:-@iluolyx/aamp-feishu-bridge@0.1.52-dev.5}"
110
+ AAMP_TASK_DEFAULT_ACP_BRIDGE_PKG="$ACP_BRIDGE_PKG"
111
+ AAMP_TASK_DEFAULT_FEISHU_BRIDGE_PKG="$FEISHU_BRIDGE_PKG"
112
+ AAMP_TASK_DEFAULT_AIME_ACP_PKG="$AIME_ACP_PKG"
67
113
  AAMP_TASK_AGENT_NAME="${AAMP_TASK_AGENT_NAME:-@larktask/aamp-feishu-task-agent}"
68
114
  AAMP_TASK_AGENT_LEGACY_NAME="${AAMP_TASK_AGENT_LEGACY_NAME:-@zengxingyuan/aamp-feishu-task-agent}"
69
- AAMP_TASK_AGENT_VERSION="0.1.0-dev.174"
115
+ AAMP_TASK_AGENT_VERSION="0.1.1-dev.3"
70
116
  AAMP_TASK_AGENT_CHANNEL="${AAMP_TASK_AGENT_CHANNEL:-dev}"
71
117
  AAMP_STALE_PROCESS_CLEANUP="${AAMP_STALE_PROCESS_CLEANUP:-false}"
72
118
  AAMP_STALE_PROCESS_SECONDS="${AAMP_STALE_PROCESS_SECONDS:-86400}"
@@ -86,8 +132,12 @@ CODEM_SERVICE_START_OUTPUT=""
86
132
  CODEM_AUTO_UPDATE_DONE="false"
87
133
  CODEM_FORCE_LOGIN_DONE="false"
88
134
  CODEM_PROVIDER_RECOVERY_DONE="false"
135
+ TRAE_CLI_BIN=""
136
+ TRAECODE_CLI_BIN=""
89
137
  PAIRING_URL=""
90
138
  ACP_AGENT_COMMAND=""
139
+ AGENT_PREPARE_CANCELLED="false"
140
+ AGENT_PREPARE_CANCEL_REASON=""
91
141
  STARTED_BRIDGE_PID=""
92
142
  ONE_CLICK_RUN_ID="$(date +%s)-$$"
93
143
  BOT_RESERVED="false"
@@ -116,6 +166,13 @@ sanitize_inherited_npm_exec_env() {
116
166
  done < <(env)
117
167
  }
118
168
 
169
+ remote_internal_helper() {
170
+ [ "$AAMP_TASK_INTERNAL" = "true" ] && {
171
+ [ "$AAMP_TASK_INTERNAL_EXECUTION_LOCATION" = "remote" ] \
172
+ || [ "$AGENT_EXECUTION_LOCATION" = "remote" ]
173
+ }
174
+ }
175
+
119
176
  usage() {
120
177
  cat <<'USAGE'
121
178
  Usage:
@@ -132,7 +189,8 @@ Running the standalone one-click script without a subcommand is the same as
132
189
  "feishu-task-agent install".
133
190
 
134
191
  Options:
135
- --agent codex|cursor Use this Agent for every new binding in the command.
192
+ --agent codex|cursor|coco|traex|traecli|workbuddy|workbuddy_ai|aime
193
+ Use this Agent for every new binding in the command.
136
194
  --aamp-host URL AAMP service URL. Default: https://meshmail.ai
137
195
  --debug Enable debug mode for bridge processes
138
196
  -h, --help Show this help
@@ -792,6 +850,7 @@ task_agent_global_install_is_complete() {
792
850
  [ -x "$NPM_GLOBAL_PREFIX/bin/aamp-logs" ] || return 1
793
851
  [ -r "$package_dir/bootstrap/aamp-feishu-task-agent-bootstrap.sh" ] || return 1
794
852
  [ -r "$package_dir/bin/feishu-task-agent-controller.mjs" ] || return 1
853
+ [ -r "$package_dir/bin/traecode-readiness.mjs" ] || return 1
795
854
  }
796
855
 
797
856
  task_agent_global_install_is_current() {
@@ -816,7 +875,7 @@ ensure_task_agent_global_install_locked() {
816
875
  fi
817
876
  remove_legacy_task_agent_global_install || return 1
818
877
  agent_detail "synchronizing task-agent npm package: current=${installed_version:-missing} expected=$expected_version"
819
- npm_install_global "$AAMP_TASK_AGENT_NAME@$expected_version" >>"$ONE_CLICK_LOG" 2>&1 || return 1
878
+ npm_install_global "$AAMP_TASK_AGENT_NAME@$expected_version" --force >>"$ONE_CLICK_LOG" 2>&1 || return 1
820
879
  task_agent_global_install_is_current "$expected_version"
821
880
  }
822
881
 
@@ -830,12 +889,16 @@ ensure_task_agent_global_install() {
830
889
 
831
890
  short_command_is_current() {
832
891
  local target="$1"
892
+ local source_file="${2:-}"
833
893
  local installed_name installed_version
834
894
  [ -x "$target" ] || return 1
835
895
  installed_name="$(script_file_task_agent_name "$target" || true)"
836
896
  installed_version="$(script_file_task_agent_version "$target" || true)"
837
897
  [ "$installed_name" = "$AAMP_TASK_AGENT_NAME" ] || return 1
838
- [ "$installed_version" = "$AAMP_TASK_AGENT_VERSION" ]
898
+ [ "$installed_version" = "$AAMP_TASK_AGENT_VERSION" ] || return 1
899
+ if [ -n "$source_file" ] && [ -r "$source_file" ]; then
900
+ cmp -s "$source_file" "$target"
901
+ fi
839
902
  }
840
903
 
841
904
  write_task_update_cache() {
@@ -913,13 +976,13 @@ install_short_command_current() {
913
976
  local target="$1"
914
977
  local source_file="${BASH_SOURCE[0]:-}"
915
978
  local tmp="${target}.tmp.$$"
916
- if short_command_is_current "$target"; then
917
- agent_detail "short command already current: $target"
918
- return 0
919
- fi
920
979
  case "$source_file" in
921
980
  ""|/dev/fd/*|/private/dev/fd/*|/proc/*) source_file="" ;;
922
981
  esac
982
+ if short_command_is_current "$target" "$source_file"; then
983
+ agent_detail "short command already current: $target"
984
+ return 0
985
+ fi
923
986
  if [ -n "$source_file" ] && [ -f "$source_file" ] && [ -r "$source_file" ]; then
924
987
  mkdir -p "$(dirname "$target")"
925
988
  cp "$source_file" "$tmp" 2>>"$ONE_CLICK_LOG" || return 1
@@ -950,6 +1013,7 @@ ensure_selected_agent_for_start() {
950
1013
  select_agent_interactively
951
1014
  fi
952
1015
  validate_agent_name "$AGENT"
1016
+ ensure_agent_selection_available "$AGENT"
953
1017
  }
954
1018
 
955
1019
  resolve_latest_task_agent_version() {
@@ -1034,12 +1098,109 @@ run_task_agent_update_command() {
1034
1098
  install_aamp_logs_bin "$installed_version" || agent_fail "failed to synchronize aamp-logs $installed_version"
1035
1099
  printf '当前版本:%s\n' "$installed_version"
1036
1100
  }
1037
- npm_install_global() {
1101
+ classify_remote_npm_cache_error() {
1102
+ node -e '
1103
+ let carry = "";
1104
+ let lineSawEnoent = false;
1105
+ let lineSawTarballData = false;
1106
+ let matched = false;
1107
+ const scan = (part, endOfLine) => {
1108
+ const text = (carry + part).toLowerCase();
1109
+ const sawEnoent = lineSawEnoent || text.includes("enoent");
1110
+ const sawTarballData = lineSawTarballData || text.includes("tarball data");
1111
+ if (text.includes("invalid response body")
1112
+ || text.includes("_cacache")
1113
+ || (sawEnoent && text.includes("content-v2"))
1114
+ || (sawTarballData && text.includes("corrupted"))
1115
+ || text.includes("tar_bad_archive")
1116
+ || text.includes("zlib: unexpected end of file")) matched = true;
1117
+ lineSawEnoent = sawEnoent;
1118
+ lineSawTarballData = sawTarballData;
1119
+ carry = text.slice(-64);
1120
+ if (endOfLine) {
1121
+ carry = "";
1122
+ lineSawEnoent = false;
1123
+ lineSawTarballData = false;
1124
+ }
1125
+ };
1126
+ process.stdin.on("data", (chunk) => {
1127
+ const parts = chunk.toString("latin1").split(/\r?\n/);
1128
+ parts.forEach((part, index) => scan(part, index < parts.length - 1));
1129
+ });
1130
+ process.stdin.on("end", () => { process.exitCode = matched ? 0 : 1; });
1131
+ process.stdin.on("error", () => { process.exitCode = 2; });
1132
+ '
1133
+ }
1134
+
1135
+ REMOTE_NPM_INSTALL_STATUS=1
1136
+ REMOTE_NPM_CACHE_ERROR=false
1137
+ run_remote_npm_install_attempt() {
1138
+ local registry="$1"
1139
+ shift
1140
+ local pipeline_status=()
1141
+ sanitize_inherited_npm_exec_env
1142
+ set +e
1143
+ "$NPM_BIN" install -g \
1144
+ --registry "$registry" \
1145
+ --cache "$NPM_CACHE_DIR" \
1146
+ --prefix "$NPM_GLOBAL_PREFIX" \
1147
+ "$@" 2>&1 | classify_remote_npm_cache_error
1148
+ pipeline_status=("${PIPESTATUS[@]}")
1149
+ set -e
1150
+ REMOTE_NPM_INSTALL_STATUS="${pipeline_status[0]:-1}"
1151
+ REMOTE_NPM_CACHE_ERROR=false
1152
+ if [ "${pipeline_status[1]:-2}" -eq 0 ]; then
1153
+ REMOTE_NPM_CACHE_ERROR=true
1154
+ fi
1155
+ return 0
1156
+ }
1157
+
1158
+ npm_install_global_from_registry_remote() {
1159
+ local registry="$1"
1160
+ shift
1161
+ finish_remote_npm_install() {
1162
+ trap - EXIT HUP INT TERM
1163
+ unset -f finish_remote_npm_install
1164
+ }
1165
+ trap finish_remote_npm_install EXIT
1166
+ trap 'exit 129' HUP
1167
+ trap 'exit 130' INT
1168
+ trap 'exit 143' TERM
1169
+ run_remote_npm_install_attempt "$registry" "$@"
1170
+ if [ "$REMOTE_NPM_INSTALL_STATUS" -eq 0 ]; then
1171
+ hash -r 2>/dev/null || true
1172
+ finish_remote_npm_install
1173
+ return 0
1174
+ fi
1175
+
1176
+ if [ "$REMOTE_NPM_CACHE_ERROR" != "true" ]; then
1177
+ finish_remote_npm_install
1178
+ return 1
1179
+ fi
1180
+
1181
+ reset_npm_cache_for_retry
1182
+ run_remote_npm_install_attempt "$registry" "$@"
1183
+ if [ "$REMOTE_NPM_INSTALL_STATUS" -ne 0 ]; then
1184
+ finish_remote_npm_install
1185
+ return 1
1186
+ fi
1187
+ hash -r 2>/dev/null || true
1188
+ finish_remote_npm_install
1189
+ }
1190
+
1191
+ npm_install_global_from_registry() {
1192
+ if remote_internal_helper; then
1193
+ npm_install_global_from_registry_remote "$@"
1194
+ return $?
1195
+ fi
1196
+
1197
+ local registry="$1"
1198
+ shift
1038
1199
  local npm_log
1039
1200
  npm_log="$(mktemp "${TMPDIR:-/tmp}/aamp-npm-install.XXXXXX")"
1040
1201
  sanitize_inherited_npm_exec_env
1041
1202
  if "$NPM_BIN" install -g \
1042
- --registry "$NPM_REGISTRY" \
1203
+ --registry "$registry" \
1043
1204
  --cache "$NPM_CACHE_DIR" \
1044
1205
  --prefix "$NPM_GLOBAL_PREFIX" \
1045
1206
  "$@" >"$npm_log" 2>&1; then
@@ -1057,7 +1218,7 @@ npm_install_global() {
1057
1218
  npm_log="$(mktemp "${TMPDIR:-/tmp}/aamp-npm-install-retry.XXXXXX")"
1058
1219
  sanitize_inherited_npm_exec_env
1059
1220
  "$NPM_BIN" install -g \
1060
- --registry "$NPM_REGISTRY" \
1221
+ --registry "$registry" \
1061
1222
  --cache "$NPM_CACHE_DIR" \
1062
1223
  --prefix "$NPM_GLOBAL_PREFIX" \
1063
1224
  "$@" >"$npm_log" 2>&1 || {
@@ -1068,6 +1229,10 @@ npm_install_global() {
1068
1229
  hash -r 2>/dev/null || true
1069
1230
  }
1070
1231
 
1232
+ npm_install_global() {
1233
+ npm_install_global_from_registry "$NPM_REGISTRY" "$@"
1234
+ }
1235
+
1071
1236
  npx_package() {
1072
1237
  local npm_log
1073
1238
  local status
@@ -1177,13 +1342,32 @@ npm_install_register_helper() {
1177
1342
  }
1178
1343
  }
1179
1344
 
1345
+ aime_internal_network_reachable() {
1346
+ command -v ping >/dev/null 2>&1 || return 1
1347
+ if is_macos; then
1348
+ ping -c 1 -W 1000 aime.bytedance.net >/dev/null 2>&1
1349
+ else
1350
+ ping -c 1 -W 1 aime.bytedance.net >/dev/null 2>&1
1351
+ fi
1352
+ }
1353
+
1180
1354
  validate_agent_name() {
1181
1355
  case "$1" in
1182
- codex|cursor) ;;
1183
- *) agent_fail "--agent must be codex or cursor" ;;
1356
+ codex|cursor|coco|traex|traecli|workbuddy|workbuddy_ai|aime) ;;
1357
+ *) agent_fail "--agent must be codex, cursor, coco, traex, traecli, workbuddy, workbuddy_ai, or aime" ;;
1184
1358
  esac
1185
1359
  }
1186
1360
 
1361
+ ensure_agent_selection_available() {
1362
+ [ "$1" = "aime" ] || return 0
1363
+ aime_internal_network_reachable \
1364
+ || agent_fail "AIME 仅在公司内网可用;当前无法 ping 通 aime.bytedance.net。"
1365
+ }
1366
+
1367
+ agent_display_name() {
1368
+ printf '%s' "$1"
1369
+ }
1370
+
1187
1371
  read_tty_line() {
1188
1372
  local prompt="$1"
1189
1373
  local value
@@ -1203,6 +1387,24 @@ agent_cli_detected() {
1203
1387
  cursor)
1204
1388
  find_cursor_agent_cli >/dev/null 2>&1
1205
1389
  ;;
1390
+ traex)
1391
+ find_traex_cli >/dev/null 2>&1
1392
+ ;;
1393
+ coco)
1394
+ find_legacy_trae_cli >/dev/null 2>&1
1395
+ ;;
1396
+ traecli)
1397
+ find_traecode_cli >/dev/null 2>&1
1398
+ ;;
1399
+ workbuddy)
1400
+ find_workbuddy_cli >/dev/null 2>&1
1401
+ ;;
1402
+ workbuddy_ai)
1403
+ find_workbuddy_ai_cli >/dev/null 2>&1
1404
+ ;;
1405
+ aime)
1406
+ aime_internal_network_reachable
1407
+ ;;
1206
1408
  *)
1207
1409
  return 1
1208
1410
  ;;
@@ -1217,9 +1419,25 @@ discover_interactive_agents() {
1217
1419
  if agent_cli_detected cursor; then
1218
1420
  DETECTED_AGENTS+=("cursor")
1219
1421
  fi
1422
+ if agent_cli_detected traex; then
1423
+ DETECTED_AGENTS+=("traex")
1424
+ elif agent_cli_detected coco; then
1425
+ DETECTED_AGENTS+=("coco")
1426
+ elif agent_cli_detected traecli; then
1427
+ DETECTED_AGENTS+=("traecli")
1428
+ fi
1429
+ if agent_cli_detected workbuddy; then
1430
+ DETECTED_AGENTS+=("workbuddy")
1431
+ fi
1432
+ if agent_cli_detected workbuddy_ai; then
1433
+ DETECTED_AGENTS+=("workbuddy_ai")
1434
+ fi
1435
+ if agent_cli_detected aime; then
1436
+ DETECTED_AGENTS+=("aime")
1437
+ fi
1220
1438
 
1221
1439
  if [ "${#DETECTED_AGENTS[@]}" -eq 0 ]; then
1222
- agent_fail "暂未检测到本地智能体。请先安装并登录 Codex 或 Cursor CLI 后重试。"
1440
+ agent_fail "暂未检测到本地智能体。请先安装 Codex、Cursor、Trae CLI、WorkBuddy WorkBuddy AI 后重试。"
1223
1441
  fi
1224
1442
  }
1225
1443
 
@@ -1238,9 +1456,9 @@ render_agent_menu() {
1238
1456
  for index in "${!DETECTED_AGENTS[@]}"; do
1239
1457
  printf '\033[2K\r' >&3
1240
1458
  if [ "$index" -eq "$selected" ]; then
1241
- printf ' > %s\n' "${DETECTED_AGENTS[$index]}" >&3
1459
+ printf ' > %s\n' "$(agent_display_name "${DETECTED_AGENTS[$index]}")" >&3
1242
1460
  else
1243
- printf ' %s\n' "${DETECTED_AGENTS[$index]}" >&3
1461
+ printf ' %s\n' "$(agent_display_name "${DETECTED_AGENTS[$index]}")" >&3
1244
1462
  fi
1245
1463
  done
1246
1464
  printf '\033[2K\r使用 ↑/↓ 选择,回车确认。也可按数字键或 j/k。\n' >&3
@@ -1254,7 +1472,7 @@ select_agent_interactively() {
1254
1472
  discover_interactive_agents
1255
1473
 
1256
1474
  if ! exec 3<>/dev/tty; then
1257
- agent_fail "missing --agent and no interactive terminal is available; pass --agent codex|cursor"
1475
+ agent_fail "missing --agent and no interactive terminal is available; pass --agent codex|cursor|coco|traex|traecli|workbuddy|workbuddy_ai|aime"
1258
1476
  fi
1259
1477
 
1260
1478
  tty_state="$(stty -g <&3)"
@@ -1440,7 +1658,7 @@ parse_args() {
1440
1658
  fi
1441
1659
 
1442
1660
  case "${1:-}" in
1443
- install|start|list|add|remove|update|help|__discover-agents|__register-binding|__prepare-agent|__ensure-profile)
1661
+ install|start|list|add|remove|update|help|__discover-agents|__register-binding|__prepare-agent|__probe-profile|__ensure-profile)
1444
1662
  AAMP_TASK_ACTION="$1"
1445
1663
  shift
1446
1664
  ;;
@@ -1598,12 +1816,13 @@ for (const bot of bots) {
1598
1816
  const profile = String(bot?.profile || "").trim();
1599
1817
  const name = String(bot?.display_name || appId).trim();
1600
1818
  const appSecret = String(bot?.app_secret || "").trim();
1819
+ const tenantBrand = bot?.tenant_brand === "lark" ? "lark" : "feishu";
1601
1820
  if (!appId || !profile || seen.has(appId) || activeAppIds.has(appId)) continue;
1602
1821
  if (!appSecret) {
1603
1822
  continue;
1604
1823
  }
1605
1824
  seen.add(appId);
1606
- console.log([appId, name, profile, appSecret].join("\t"));
1825
+ console.log([appId, name, profile, appSecret, tenantBrand].join("\t"));
1607
1826
  }
1608
1827
  '
1609
1828
  }
@@ -1786,7 +2005,12 @@ process.exit(bots.some((bot) => String(bot?.app_id || "").trim() && String(bot?.
1786
2005
 
1787
2006
  task_profile_name_for_app_id() {
1788
2007
  local app_id="$1"
1789
- printf 'aamp-feishu-task-%s' "$app_id"
2008
+ local tenant_brand="${2:-feishu}"
2009
+ case "$tenant_brand" in
2010
+ feishu) printf 'aamp-feishu-task-%s' "$app_id" ;;
2011
+ lark) printf 'aamp-feishu-task-%s-lark' "$app_id" ;;
2012
+ *) agent_fail "unsupported tenant brand: $tenant_brand" ;;
2013
+ esac
1790
2014
  }
1791
2015
 
1792
2016
  save_bot_config() {
@@ -1794,19 +2018,27 @@ save_bot_config() {
1794
2018
  local app_id="$2"
1795
2019
  local profile="$3"
1796
2020
  local app_secret="${4:-}"
2021
+ local tenant_brand="${5:-feishu}"
1797
2022
  mkdir -p "$(dirname "$BOT_CONFIG_FILE")"
1798
- BOT_CONFIG_FILE="$BOT_CONFIG_FILE" BOT_NAME="$bot_name" BOT_APP_ID="$app_id" BOT_PROFILE="$profile" BOT_APP_SECRET="$app_secret" FEISHU_USER_AUTH_DOMAINS="$FEISHU_USER_AUTH_DOMAINS" node -e '
2023
+ case "$tenant_brand" in
2024
+ feishu|lark) ;;
2025
+ *) agent_fail "unsupported tenant brand: $tenant_brand" ;;
2026
+ esac
2027
+ BOT_CONFIG_FILE="$BOT_CONFIG_FILE" BOT_NAME="$bot_name" BOT_APP_ID="$app_id" BOT_PROFILE="$profile" BOT_APP_SECRET="$app_secret" BOT_TENANT_BRAND="$tenant_brand" FEISHU_TASK_PROFILE_DOMAINS="$FEISHU_TASK_PROFILE_DOMAINS" FEISHU_SCOPE_MANIFEST_VERSION="$FEISHU_SCOPE_MANIFEST_VERSION" FEISHU_USER_AUTH_MODE="$FEISHU_USER_AUTH_MODE" node -e '
1799
2028
  const fs = require("fs");
1800
2029
  const file = process.env.BOT_CONFIG_FILE;
1801
2030
  const appSecret = String(process.env.BOT_APP_SECRET || "").trim();
1802
2031
  const next = {
1803
2032
  display_name: process.env.BOT_NAME || process.env.BOT_APP_ID,
1804
2033
  app_id: process.env.BOT_APP_ID,
2034
+ tenant_brand: process.env.BOT_TENANT_BRAND || "feishu",
1805
2035
  ...(appSecret ? { app_secret: appSecret } : {}),
1806
2036
  profile: process.env.BOT_PROFILE,
1807
2037
  auth_mode: "lark-cli",
1808
2038
  capabilities: ["im", "task"],
1809
- domains: String(process.env.FEISHU_USER_AUTH_DOMAINS || "").split(",").map((item) => item.trim()).filter(Boolean),
2039
+ domains: String(process.env.FEISHU_TASK_PROFILE_DOMAINS || "task").split(",").map((item) => item.trim()).filter(Boolean),
2040
+ scope_manifest_version: Number(process.env.FEISHU_SCOPE_MANIFEST_VERSION || 0),
2041
+ user_auth_mode: process.env.FEISHU_USER_AUTH_MODE || "optional",
1810
2042
  updated_at: new Date().toISOString(),
1811
2043
  };
1812
2044
  let parsed = { version: 1, profiles: [] };
@@ -2039,15 +2271,65 @@ ensure_lark_cli_min_version() {
2039
2271
  select_lark_cli_bin
2040
2272
  }
2041
2273
 
2274
+ feishu_scope_manifest_json() {
2275
+ cat <<'JSON'
2276
+ {"version":2,"app":{"tenant":["im:message","im:message:send_as_bot","im:message:readonly","im:resource","cardkit:card:write","task:task","task:comment","task:task:readonly","task:comment:readonly","task:attachment:read","task:attachment:write","task:comment:read","task:comment:write","task:task:read","task:task:write","task:tasklist:read","task:tasklist:write","task:custom_field:read","task:custom_field:write","task:section:read","task:section:write"],"user":["im:message","im:message:readonly","im:resource","cardkit:card:write","task:task","task:comment","task:task:readonly","task:comment:readonly","task:attachment:read","task:attachment:write","task:comment:read","task:comment:write","task:task:read","task:task:write","task:tasklist:read","task:tasklist:write","task:custom_field:read","task:custom_field:write","task:section:read","task:section:write"]},"userAuth":{"core":[],"optional":["task:task","task:comment","task:task:readonly","task:comment:readonly","task:attachment:read","task:attachment:write","task:comment:read","task:comment:write","task:task:read","task:task:write","task:tasklist:read","task:tasklist:write","task:custom_field:read","task:custom_field:write","task:section:read","task:section:write"]}}
2277
+ JSON
2278
+ }
2279
+
2280
+ initialize_feishu_scope_manifest() {
2281
+ local manifest_values=()
2282
+ local value
2283
+
2284
+ while IFS= read -r value; do
2285
+ manifest_values+=("$value")
2286
+ done < <(feishu_scope_manifest_json | node -e '
2287
+ const fs = require("fs");
2288
+ const manifest = JSON.parse(fs.readFileSync(0, "utf8"));
2289
+ console.log(String(manifest.version));
2290
+ console.log(manifest.app.tenant.join(","));
2291
+ console.log(manifest.app.user.join(","));
2292
+ console.log(manifest.userAuth.core.join(" "));
2293
+ console.log(manifest.userAuth.optional.join(" "));
2294
+ ')
2295
+
2296
+ [ "${#manifest_values[@]}" -eq 5 ] || agent_fail "invalid embedded Feishu scope manifest"
2297
+ FEISHU_SCOPE_MANIFEST_VERSION="${FEISHU_SCOPE_MANIFEST_VERSION:-${manifest_values[0]}}"
2298
+ FEISHU_APP_SCOPES_TENANT="${FEISHU_APP_SCOPES_TENANT:-${manifest_values[1]}}"
2299
+ FEISHU_APP_SCOPES_USER="${FEISHU_APP_SCOPES_USER:-${manifest_values[2]}}"
2300
+ FEISHU_USER_AUTH_CORE_SCOPES="${FEISHU_USER_AUTH_CORE_SCOPES:-${manifest_values[3]}}"
2301
+ FEISHU_USER_AUTH_OPTIONAL_SCOPES="${FEISHU_USER_AUTH_OPTIONAL_SCOPES:-${manifest_values[4]}}"
2302
+ if [ -n "${FEISHU_USER_AUTH_REQUIRED_SCOPES:-}" ]; then
2303
+ FEISHU_USER_AUTH_CORE_SCOPES="$FEISHU_USER_AUTH_REQUIRED_SCOPES"
2304
+ else
2305
+ FEISHU_USER_AUTH_REQUIRED_SCOPES="$FEISHU_USER_AUTH_CORE_SCOPES"
2306
+ fi
2307
+ if [ -z "${FEISHU_USER_AUTH_REQUESTED_SCOPES:-}" ]; then
2308
+ if [ -n "${AAMP_FEISHU_USER_AUTH_REQUIRED_SCOPES_OVERRIDE:-}" ]; then
2309
+ FEISHU_USER_AUTH_REQUESTED_SCOPES="$FEISHU_USER_AUTH_REQUIRED_SCOPES"
2310
+ else
2311
+ FEISHU_USER_AUTH_REQUESTED_SCOPES="${FEISHU_USER_AUTH_CORE_SCOPES} ${FEISHU_USER_AUTH_OPTIONAL_SCOPES}"
2312
+ fi
2313
+ fi
2314
+ export FEISHU_SCOPE_MANIFEST_VERSION
2315
+ export FEISHU_APP_SCOPES_TENANT FEISHU_APP_SCOPES_USER
2316
+ export FEISHU_USER_AUTH_CORE_SCOPES FEISHU_USER_AUTH_OPTIONAL_SCOPES
2317
+ export FEISHU_USER_AUTH_REQUIRED_SCOPES FEISHU_USER_AUTH_REQUESTED_SCOPES
2318
+ }
2319
+
2042
2320
  lark_cli_user_auth_satisfied() {
2043
2321
  local profile="$1"
2322
+ local required_scopes="${2:-${FEISHU_USER_AUTH_REQUIRED_SCOPES:-${FEISHU_USER_AUTH_CORE_SCOPES:-}}}"
2044
2323
  local status_json auth_check
2045
2324
  LARK_CLI_CMD="${LARK_CLI_CMD:-lark-cli}"
2046
2325
 
2326
+ if [ -z "${required_scopes//[[:space:],]/}" ]; then
2327
+ return 0
2328
+ fi
2047
2329
  status_json="$("$LARK_CLI_CMD" --profile "$profile" auth status --json 2>/dev/null)" || return 1
2048
2330
 
2049
2331
  set +e
2050
- auth_check="$(AUTH_STATUS_JSON="$status_json" FEISHU_USER_AUTH_REQUIRED_SCOPES="$FEISHU_USER_AUTH_REQUIRED_SCOPES" FEISHU_USER_AUTH_EXCLUDES="$FEISHU_USER_AUTH_EXCLUDES" node -e '
2332
+ auth_check="$(AUTH_STATUS_JSON="$status_json" FEISHU_USER_AUTH_REQUIRED_SCOPES="$required_scopes" FEISHU_USER_AUTH_EXCLUDES="$FEISHU_USER_AUTH_EXCLUDES" node -e '
2051
2333
  function parseJsonOutput(value, label) {
2052
2334
  const raw = String(value || "");
2053
2335
  const start = raw.indexOf("{");
@@ -2088,10 +2370,76 @@ console.log("ok");
2088
2370
  return 1
2089
2371
  }
2090
2372
 
2373
+ write_lark_cli_auth_capability_snapshot() {
2374
+ local profile="$1"
2375
+ local status_json="{}"
2376
+ local state_dir="$AAMP_FEISHU_AUTH_STATE_DIR"
2377
+ local safe_profile state_file temp_file
2378
+
2379
+ set +e
2380
+ status_json="$("$LARK_CLI_CMD" --profile "$profile" auth status --json 2>/dev/null)"
2381
+ local status_code=$?
2382
+ set -e
2383
+ if [ "$status_code" -ne 0 ] || [ -z "$status_json" ]; then
2384
+ status_json="{}"
2385
+ fi
2386
+
2387
+ mkdir -p "$state_dir" || return 1
2388
+ safe_profile="$(PROFILE="$profile" node -e 'process.stdout.write(String(process.env.PROFILE || "profile").replace(/[^A-Za-z0-9._-]/g, "_"))')" || return 1
2389
+ state_file="$state_dir/$safe_profile.json"
2390
+ temp_file="$(mktemp "$state_dir/.auth-capabilities.XXXXXX")" || return 1
2391
+ if ! AUTH_STATUS_JSON="$status_json" \
2392
+ AUTH_PROFILE="$profile" \
2393
+ FEISHU_SCOPE_MANIFEST_VERSION="$FEISHU_SCOPE_MANIFEST_VERSION" \
2394
+ FEISHU_USER_AUTH_CORE_SCOPES="$FEISHU_USER_AUTH_CORE_SCOPES" \
2395
+ FEISHU_USER_AUTH_OPTIONAL_SCOPES="$FEISHU_USER_AUTH_OPTIONAL_SCOPES" \
2396
+ node -e '
2397
+ const raw = String(process.env.AUTH_STATUS_JSON || "{}");
2398
+ const start = raw.indexOf("{");
2399
+ let status = {};
2400
+ try { status = JSON.parse(start >= 0 ? raw.slice(start) : "{}"); } catch {}
2401
+ const user = status?.identities?.user;
2402
+ const usable = Boolean(user?.available) && new Set(["valid", "needs_refresh"]).has(user?.tokenStatus);
2403
+ const split = (value) => [...new Set(String(value || "").split(/[\s,]+/).map((item) => item.trim()).filter(Boolean))];
2404
+ const granted = new Set(split(user?.scope));
2405
+ const core = split(process.env.FEISHU_USER_AUTH_CORE_SCOPES);
2406
+ const optional = split(process.env.FEISHU_USER_AUTH_OPTIONAL_SCOPES);
2407
+ const missingCoreScopes = core.filter((scope) => !granted.has(scope));
2408
+ const missingOptionalScopes = optional.filter((scope) => !granted.has(scope));
2409
+ process.stdout.write(JSON.stringify({
2410
+ schemaVersion: 1,
2411
+ manifestVersion: Number(process.env.FEISHU_SCOPE_MANIFEST_VERSION || 0),
2412
+ profile: process.env.AUTH_PROFILE,
2413
+ tokenStatus: user?.tokenStatus || "missing",
2414
+ grantedScopes: [...granted],
2415
+ missingCoreScopes,
2416
+ missingOptionalScopes,
2417
+ capabilities: { task_user: usable && missingOptionalScopes.length === 0 },
2418
+ checkedAt: new Date().toISOString(),
2419
+ }, null, 2));
2420
+ ' >"$temp_file"; then
2421
+ rm -f "$temp_file"
2422
+ return 1
2423
+ fi
2424
+ if ! mv "$temp_file" "$state_file"; then
2425
+ rm -f "$temp_file"
2426
+ return 1
2427
+ fi
2428
+ chmod 600 "$state_file" 2>/dev/null || true
2429
+ }
2430
+
2431
+ persist_lark_cli_auth_capability_snapshot() {
2432
+ local profile="$1"
2433
+ if ! write_lark_cli_auth_capability_snapshot "$profile"; then
2434
+ agent_log "warning: unable to persist optional user capability snapshot for profile: $profile"
2435
+ fi
2436
+ }
2437
+
2091
2438
  normalize_lark_cli_auth_excludes() {
2092
- EXCLUDES="$1" node -e '
2439
+ EXCLUDES="$1" REQUESTED_SCOPES="${2:-${FEISHU_USER_AUTH_REQUESTED_SCOPES:-}}" node -e '
2093
2440
  const excludes = String(process.env.EXCLUDES || "").split(",").map((item) => item.trim()).filter(Boolean);
2094
- console.log([...new Set(excludes)].join(","));
2441
+ const requested = new Set(String(process.env.REQUESTED_SCOPES || "").split(/[\s,]+/).map((item) => item.trim()).filter(Boolean));
2442
+ console.log([...new Set(excludes)].filter((scope) => requested.has(scope)).join(","));
2095
2443
  '
2096
2444
  }
2097
2445
 
@@ -2238,12 +2586,14 @@ run_lark_cli_auth_login_with_browser_open() {
2238
2586
  run_lark_cli_auth_login() {
2239
2587
  local profile="$1"
2240
2588
  local auth_excludes="$2"
2589
+ local requested_scopes="${FEISHU_USER_AUTH_REQUESTED_SCOPES:-}"
2241
2590
 
2242
- auth_excludes="$(normalize_lark_cli_auth_excludes "$auth_excludes")"
2591
+ [ -n "${requested_scopes//[[:space:],]/}" ] || return 0
2592
+ auth_excludes="$(normalize_lark_cli_auth_excludes "$auth_excludes" "$requested_scopes")"
2243
2593
  if [ -z "$auth_excludes" ]; then
2244
- run_lark_cli_auth_login_with_browser_open "$LARK_CLI_CMD" --profile "$profile" auth login --domain "$FEISHU_USER_AUTH_DOMAINS" --scope "$FEISHU_USER_AUTH_REQUIRED_SCOPES"
2594
+ run_lark_cli_auth_login_with_browser_open "$LARK_CLI_CMD" --profile "$profile" auth login --scope "$requested_scopes"
2245
2595
  else
2246
- run_lark_cli_auth_login_with_browser_open "$LARK_CLI_CMD" --profile "$profile" auth login --domain "$FEISHU_USER_AUTH_DOMAINS" --scope "$FEISHU_USER_AUTH_REQUIRED_SCOPES" --exclude "$auth_excludes"
2596
+ run_lark_cli_auth_login_with_browser_open "$LARK_CLI_CMD" --profile "$profile" auth login --scope "$requested_scopes" --exclude "$auth_excludes"
2247
2597
  fi
2248
2598
  }
2249
2599
 
@@ -2252,11 +2602,48 @@ ensure_lark_cli_profile() {
2252
2602
  with_lark_cli_config_lock ensure_lark_cli_profile_locked "$@"
2253
2603
  }
2254
2604
 
2605
+ validate_feishu_user_auth_mode() {
2606
+ case "$FEISHU_USER_AUTH_MODE" in
2607
+ optional|required|disabled) ;;
2608
+ *) agent_fail "FEISHU_USER_AUTH_MODE must be optional, required, or disabled" ;;
2609
+ esac
2610
+ }
2611
+
2612
+ probe_lark_cli_profile_locked() {
2613
+ local profile="$1"
2614
+ local required_scopes
2615
+
2616
+ initialize_feishu_scope_manifest
2617
+ validate_feishu_user_auth_mode
2618
+ "$LARK_CLI_CMD" profile list 2>/dev/null | grep -F "\"$profile\"" >/dev/null 2>&1 \
2619
+ || return 1
2620
+ if [ "$FEISHU_USER_AUTH_MODE" = "disabled" ]; then
2621
+ persist_lark_cli_auth_capability_snapshot "$profile"
2622
+ return 0
2623
+ fi
2624
+ if [ "$FEISHU_USER_AUTH_MODE" = "required" ]; then
2625
+ required_scopes="$FEISHU_USER_AUTH_REQUESTED_SCOPES"
2626
+ else
2627
+ required_scopes="$FEISHU_USER_AUTH_CORE_SCOPES"
2628
+ fi
2629
+ lark_cli_user_auth_satisfied "$profile" "$required_scopes" || return 1
2630
+ persist_lark_cli_auth_capability_snapshot "$profile"
2631
+ }
2632
+
2255
2633
  ensure_lark_cli_profile_locked() {
2256
2634
  local app_id="$1"
2257
2635
  local app_secret="$2"
2258
2636
  local profile="$3"
2637
+ local tenant_brand="${4:-feishu}"
2259
2638
  local auth_excludes
2639
+ local required_scopes
2640
+
2641
+ initialize_feishu_scope_manifest
2642
+ validate_feishu_user_auth_mode
2643
+ case "$tenant_brand" in
2644
+ feishu|lark) ;;
2645
+ *) agent_fail "unsupported tenant brand: $tenant_brand" ;;
2646
+ esac
2260
2647
 
2261
2648
  if "$LARK_CLI_CMD" profile list 2>/dev/null | grep -F "\"$profile\"" >/dev/null 2>&1; then
2262
2649
  agent_detail "lark-cli profile already exists: $profile"
@@ -2266,16 +2653,42 @@ ensure_lark_cli_profile_locked() {
2266
2653
  printf '%s\n' "$app_secret" | "$LARK_CLI_CMD" profile add \
2267
2654
  --name "$profile" \
2268
2655
  --app-id "$app_id" \
2656
+ --brand "$tenant_brand" \
2269
2657
  --app-secret-stdin
2270
2658
  fi
2271
2659
 
2272
- agent_detail "ensuring lark-cli user auth domains for profile: $profile"
2660
+ if [ "$FEISHU_USER_AUTH_MODE" = "disabled" ]; then
2661
+ persist_lark_cli_auth_capability_snapshot "$profile"
2662
+ agent_log "Task bridge ready; optional user capabilities disabled for profile: $profile"
2663
+ return 0
2664
+ fi
2665
+
2273
2666
  auth_excludes="$FEISHU_USER_AUTH_EXCLUDES"
2274
- if lark_cli_user_auth_satisfied "$profile"; then
2275
- agent_detail "lark-cli user auth already has required scopes for profile: $profile"
2276
- else
2667
+ if [ "$FEISHU_USER_AUTH_MODE" = "required" ]; then
2668
+ required_scopes="$FEISHU_USER_AUTH_REQUESTED_SCOPES"
2669
+ agent_detail "ensuring explicit lark-cli user auth scopes for profile: $profile"
2670
+ if ! lark_cli_user_auth_satisfied "$profile" "$required_scopes"; then
2671
+ run_lark_cli_auth_login "$profile" "$auth_excludes"
2672
+ lark_cli_user_auth_satisfied "$profile" "$required_scopes" \
2673
+ || agent_fail "lark-cli user authorization did not grant the required explicit scopes"
2674
+ fi
2675
+ persist_lark_cli_auth_capability_snapshot "$profile"
2676
+ agent_detail "lark-cli user auth has the requested scopes for profile: $profile"
2677
+ return 0
2678
+ fi
2679
+
2680
+ if ! lark_cli_user_auth_satisfied "$profile" "$FEISHU_USER_AUTH_CORE_SCOPES"; then
2681
+ agent_detail "ensuring explicitly configured lark-cli core scopes for profile: $profile"
2277
2682
  run_lark_cli_auth_login "$profile" "$auth_excludes"
2683
+ lark_cli_user_auth_satisfied "$profile" "$FEISHU_USER_AUTH_CORE_SCOPES" \
2684
+ || agent_fail "lark-cli user authorization did not grant the configured core scopes"
2685
+ fi
2686
+ if lark_cli_user_auth_satisfied "$profile" "$FEISHU_USER_AUTH_OPTIONAL_SCOPES"; then
2687
+ agent_detail "lark-cli optional user capabilities are available for profile: $profile"
2688
+ else
2689
+ agent_log "Task bridge ready; optional user capabilities unavailable for profile: $profile"
2278
2690
  fi
2691
+ persist_lark_cli_auth_capability_snapshot "$profile"
2279
2692
  }
2280
2693
 
2281
2694
  forget_current_bot_after_feishu_start_failure() {
@@ -2311,17 +2724,19 @@ select_existing_bot_or_create() {
2311
2724
  local names=()
2312
2725
  local profiles=()
2313
2726
  local app_secrets=()
2727
+ local tenant_brands=()
2314
2728
  local bot_labels=()
2315
- local app_id name profile app_secret
2729
+ local app_id name profile app_secret tenant_brand
2316
2730
  local index create_index selected
2317
2731
 
2318
2732
  acquire_bot_selection_lock
2319
- while IFS=$'\t' read -r app_id name profile app_secret; do
2733
+ while IFS=$'\t' read -r app_id name profile app_secret tenant_brand; do
2320
2734
  [ -n "$app_id" ] || continue
2321
2735
  app_ids+=("$app_id")
2322
2736
  names+=("${name:-$app_id}")
2323
2737
  profiles+=("$profile")
2324
2738
  app_secrets+=("$app_secret")
2739
+ tenant_brands+=("${tenant_brand:-feishu}")
2325
2740
  bot_labels+=("${name:-$app_id} ($app_id)")
2326
2741
  done < <(load_bot_configs)
2327
2742
 
@@ -2360,11 +2775,12 @@ select_existing_bot_or_create() {
2360
2775
  BOT_NAME="${names[$index]}"
2361
2776
  LARK_CLI_PROFILE="${profiles[$index]}"
2362
2777
  APP_SECRET="${app_secrets[$index]:-}"
2778
+ APP_TENANT_BRAND="${tenant_brands[$index]:-feishu}"
2363
2779
  reserve_selected_bot "$APP_ID" "$LARK_CLI_PROFILE" "$BOT_NAME"
2364
2780
  release_bot_selection_lock
2365
2781
  agent_detail "using Feishu bot: $BOT_NAME ($APP_ID, profile=$LARK_CLI_PROFILE)"
2366
2782
  agent_log "正在检查飞书授权..."
2367
- ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE"
2783
+ ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE" "$APP_TENANT_BRAND"
2368
2784
  }
2369
2785
 
2370
2786
  register_feishu_app() {
@@ -2376,6 +2792,8 @@ register_feishu_app() {
2376
2792
  local default_name
2377
2793
  local bot_name
2378
2794
 
2795
+ initialize_feishu_scope_manifest
2796
+
2379
2797
  workdir="$(mktemp -d "${TMPDIR:-/tmp}/aamp-register-feishu-app.XXXXXX")"
2380
2798
  register_script="$workdir/register-app.mjs"
2381
2799
  if [ -n "$AAMP_RUN_LOG_DIR" ]; then
@@ -2412,6 +2830,7 @@ const userScopes = splitList(process.env.FEISHU_APP_SCOPES_USER);
2412
2830
  const tenantEvents = splitList(process.env.FEISHU_APP_EVENTS_TENANT);
2413
2831
  const userEvents = splitList(process.env.FEISHU_APP_EVENTS_USER);
2414
2832
  const appName = process.env.FEISHU_APP_PRESET_NAME || '飞书 CLI';
2833
+ let detectedTenantBrand = 'feishu';
2415
2834
 
2416
2835
  function userLog(message) {
2417
2836
  writeSync(5, `${message}\n`);
@@ -2464,15 +2883,24 @@ const result = await lark.registerApp({
2464
2883
  },
2465
2884
  onStatusChange(info) {
2466
2885
  if (info.status === 'polling') return;
2886
+ if (info.status === 'domain_switched') detectedTenantBrand = 'lark';
2467
2887
  console.log(`[aamp-one-click] registerApp status: ${info.status}`);
2468
2888
  },
2469
2889
  });
2470
2890
 
2471
- async function fetchRegisteredAppName(appId, appSecret) {
2891
+ const reportedTenantBrand = result?.user_info?.tenant_brand;
2892
+ const tenantBrand = reportedTenantBrand === undefined || reportedTenantBrand === null || reportedTenantBrand === ''
2893
+ ? detectedTenantBrand
2894
+ : reportedTenantBrand;
2895
+ const tenantBrandIsSupported = tenantBrand === 'feishu' || tenantBrand === 'lark';
2896
+ const openApiDomain = tenantBrand === 'lark' ? lark.Domain.Lark : lark.Domain.Feishu;
2897
+
2898
+ async function fetchRegisteredAppName(appId, appSecret, domain) {
2472
2899
  try {
2473
2900
  const client = new lark.Client({
2474
2901
  appId,
2475
2902
  appSecret,
2903
+ domain,
2476
2904
  });
2477
2905
  const response = await client.application.application.get({
2478
2906
  path: { app_id: appId },
@@ -2486,11 +2914,14 @@ async function fetchRegisteredAppName(appId, appSecret) {
2486
2914
  }
2487
2915
  }
2488
2916
 
2489
- const registeredAppName = await fetchRegisteredAppName(result.client_id, result.client_secret);
2917
+ const registeredAppName = tenantBrandIsSupported
2918
+ ? await fetchRegisteredAppName(result.client_id, result.client_secret, openApiDomain)
2919
+ : '';
2490
2920
  const resultPayload = {
2491
2921
  app_id: result.client_id,
2492
2922
  app_secret: result.client_secret,
2493
2923
  app_name: registeredAppName || appName,
2924
+ tenant_brand: tenantBrand,
2494
2925
  };
2495
2926
  await import('node:fs/promises').then(({ writeFile }) => writeFile(process.env.AAMP_REGISTER_APP_RESULT_FILE, JSON.stringify(resultPayload)));
2496
2927
  console.log(`[aamp-one-click] Feishu app registration completed: ${result.client_id}`);
@@ -2518,19 +2949,29 @@ NODE
2518
2949
  APP_ID="$(node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(data.app_id || "")' "$register_result_file")"
2519
2950
  APP_SECRET="$(node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(data.app_secret || "")' "$register_result_file")"
2520
2951
  bot_name="$(node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(data.app_name || "")' "$register_result_file")"
2952
+ APP_TENANT_BRAND="$(node -e 'const fs = require("fs"); const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(data.tenant_brand || "feishu")' "$register_result_file")"
2521
2953
  : >"$register_result_file"
2522
2954
  [ -n "$APP_ID" ] && [ -n "$APP_SECRET" ] || agent_fail "Feishu app registration returned incomplete credentials"
2955
+ case "$APP_TENANT_BRAND" in
2956
+ feishu|lark) ;;
2957
+ *) agent_fail "Feishu app registration returned unsupported tenant brand: $APP_TENANT_BRAND" ;;
2958
+ esac
2523
2959
 
2524
2960
  bot_name="${bot_name:-$default_name}"
2525
2961
  BOT_NAME="$bot_name"
2526
- LARK_CLI_PROFILE="$(task_profile_name_for_app_id "$APP_ID")"
2527
- ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE"
2962
+ if [ "$AGENT_EXECUTION_LOCATION" = "remote" ]; then
2963
+ LARK_CLI_PROFILE=""
2964
+ else
2965
+ LARK_CLI_PROFILE="$(task_profile_name_for_app_id "$APP_ID" "$APP_TENANT_BRAND")"
2966
+ source_lark_env
2967
+ ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE" "$APP_TENANT_BRAND"
2968
+ fi
2528
2969
  if [ "$AAMP_TASK_INTERNAL" = "true" ]; then
2529
2970
  agent_detail "Feishu Bot 已授权:$BOT_NAME ($APP_ID)"
2530
2971
  return 0
2531
2972
  fi
2532
2973
  acquire_bot_selection_lock
2533
- save_bot_config "$bot_name" "$APP_ID" "$LARK_CLI_PROFILE" "$APP_SECRET"
2974
+ save_bot_config "$bot_name" "$APP_ID" "$LARK_CLI_PROFILE" "$APP_SECRET" "$APP_TENANT_BRAND"
2534
2975
  reserve_selected_bot "$APP_ID" "$LARK_CLI_PROFILE" "$bot_name"
2535
2976
  release_bot_selection_lock
2536
2977
  agent_log "saved Feishu task profile config: $bot_name ($APP_ID, profile=$LARK_CLI_PROFILE)"
@@ -2678,6 +3119,338 @@ find_cursor_agent_cli() {
2678
3119
  return 1
2679
3120
  }
2680
3121
 
3122
+ resolve_trae_cli_candidate() {
3123
+ local candidate
3124
+ candidate="$1"
3125
+ [ -n "$candidate" ] || return 1
3126
+ if command -v "$candidate" >/dev/null 2>&1; then
3127
+ command -v "$candidate"
3128
+ return 0
3129
+ fi
3130
+ if [ -x "$candidate" ]; then
3131
+ printf '%s\n' "$candidate"
3132
+ return 0
3133
+ fi
3134
+ return 1
3135
+ }
3136
+
3137
+ find_traex_cli_candidates() {
3138
+ local candidate resolved emitted=""
3139
+ local candidates=()
3140
+ if [ -n "$TRAE_CLI_BIN" ] && [ "$(basename "$TRAE_CLI_BIN")" = "traex" ]; then candidates+=("$TRAE_CLI_BIN"); fi
3141
+ if [ -n "$AAMP_TRAE_CLI_BIN" ] && [ "$(basename "$AAMP_TRAE_CLI_BIN")" = "traex" ]; then candidates+=("$AAMP_TRAE_CLI_BIN"); fi
3142
+ candidates+=("traex")
3143
+
3144
+ for candidate in "${candidates[@]}"; do
3145
+ resolved="$(resolve_trae_cli_candidate "$candidate" || true)"
3146
+ [ -n "$resolved" ] || continue
3147
+ case "$emitted" in
3148
+ *"|$resolved|"*) continue ;;
3149
+ esac
3150
+ emitted="${emitted}|$resolved|"
3151
+ printf '%s\n' "$resolved"
3152
+ done
3153
+ }
3154
+
3155
+ find_coco_cli_candidates() {
3156
+ local candidate resolved emitted=""
3157
+ local candidates=()
3158
+ if [ -n "$TRAE_CLI_BIN" ] && [ "$(basename "$TRAE_CLI_BIN")" = "coco" ]; then candidates+=("$TRAE_CLI_BIN"); fi
3159
+ if [ -n "$AAMP_TRAE_CLI_BIN" ] && [ "$(basename "$AAMP_TRAE_CLI_BIN")" = "coco" ]; then candidates+=("$AAMP_TRAE_CLI_BIN"); fi
3160
+ candidates+=("coco")
3161
+ for candidate in "${candidates[@]}"; do
3162
+ resolved="$(resolve_trae_cli_candidate "$candidate" || true)"
3163
+ [ -n "$resolved" ] || continue
3164
+ case "$emitted" in *"|$resolved|"*) continue ;; esac
3165
+ emitted="${emitted}|$resolved|"
3166
+ printf '%s\n' "$resolved"
3167
+ done
3168
+ }
3169
+
3170
+ find_traecode_cli_candidates() {
3171
+ local candidate resolved emitted=""
3172
+ local candidates=()
3173
+ [ -n "$TRAECODE_CLI_BIN" ] && candidates+=("$TRAECODE_CLI_BIN")
3174
+ [ -n "$AAMP_TRAECODE_CLI_BIN" ] && candidates+=("$AAMP_TRAECODE_CLI_BIN")
3175
+ candidates+=("traecli")
3176
+ for candidate in "${candidates[@]}"; do
3177
+ resolved="$(resolve_trae_cli_candidate "$candidate" || true)"
3178
+ [ -n "$resolved" ] || continue
3179
+ case "$emitted" in *"|$resolved|"*) continue ;; esac
3180
+ emitted="${emitted}|$resolved|"
3181
+ printf '%s\n' "$resolved"
3182
+ done
3183
+ }
3184
+
3185
+ real_executable_path() {
3186
+ node -e 'const fs=require("fs"); try { process.stdout.write(fs.realpathSync(process.argv[1])); } catch { process.exit(1); }' "$1" 2>/dev/null
3187
+ }
3188
+
3189
+ find_traex_cli() {
3190
+ local candidate
3191
+ candidate="$(find_traex_cli_candidates | head -n 1)"
3192
+ if [ -n "$candidate" ]; then
3193
+ TRAE_CLI_BIN="$candidate"
3194
+ printf '%s\n' "$candidate"
3195
+ return 0
3196
+ fi
3197
+ return 1
3198
+ }
3199
+
3200
+ find_coco_cli() {
3201
+ local candidate
3202
+ candidate="$(find_coco_cli_candidates | head -n 1)"
3203
+ [ -n "$candidate" ] || return 1
3204
+ TRAE_CLI_BIN="$candidate"
3205
+ printf '%s\n' "$candidate"
3206
+ }
3207
+
3208
+ traecode_cli_conflicts_with_coco() {
3209
+ local traecli_bin="$1" coco_bin traecli_real coco_real
3210
+ coco_bin="$(find_coco_cli || true)"
3211
+ [ -n "$coco_bin" ] || return 1
3212
+ traecli_real="$(real_executable_path "$traecli_bin" || true)"
3213
+ coco_real="$(real_executable_path "$coco_bin" || true)"
3214
+ [ -n "$traecli_real" ] && [ "$traecli_real" = "$coco_real" ]
3215
+ }
3216
+
3217
+ find_traecode_cli() {
3218
+ local candidate explicit_candidate="" conflict_found="false"
3219
+ if [ -n "$TRAECODE_CLI_BIN" ]; then explicit_candidate="$TRAECODE_CLI_BIN"; fi
3220
+ if [ -n "$AAMP_TRAECODE_CLI_BIN" ]; then explicit_candidate="$AAMP_TRAECODE_CLI_BIN"; fi
3221
+ if [ -n "$explicit_candidate" ]; then
3222
+ case "$(basename "$explicit_candidate")" in coco|traex) return 2 ;; esac
3223
+ candidate="$(resolve_trae_cli_candidate "$explicit_candidate" || true)"
3224
+ [ -n "$candidate" ] || return 1
3225
+ if traecode_cli_conflicts_with_coco "$candidate"; then return 2; fi
3226
+ TRAECODE_CLI_BIN="$candidate"
3227
+ printf '%s\n' "$candidate"
3228
+ return 0
3229
+ fi
3230
+ while IFS= read -r candidate; do
3231
+ [ -n "$candidate" ] || continue
3232
+ if traecode_cli_conflicts_with_coco "$candidate"; then
3233
+ if [ -n "$explicit_candidate" ] && [ "$candidate" = "$explicit_candidate" ]; then return 2; fi
3234
+ conflict_found="true"
3235
+ continue
3236
+ fi
3237
+ TRAECODE_CLI_BIN="$candidate"
3238
+ printf '%s\n' "$candidate"
3239
+ return 0
3240
+ done < <(find_traecode_cli_candidates)
3241
+ [ "$conflict_found" != "true" ] || return 2
3242
+ return 1
3243
+ }
3244
+
3245
+ find_legacy_trae_cli() {
3246
+ find_coco_cli
3247
+ }
3248
+
3249
+ resolve_trae_cli() {
3250
+ case "${AGENT:-}" in
3251
+ traex) find_traex_cli ;;
3252
+ coco) find_coco_cli ;;
3253
+ traecli) find_traecode_cli ;;
3254
+ *) return 1 ;;
3255
+ esac
3256
+ }
3257
+
3258
+ find_workbuddy_cli() {
3259
+ is_macos || return 1
3260
+ [ -x "$WORKBUDDY_APP_CLI" ] || return 1
3261
+ printf '%s\n' "$WORKBUDDY_APP_CLI"
3262
+ }
3263
+
3264
+ find_workbuddy_ai_cli() {
3265
+ is_macos || return 1
3266
+ [ -x "$WORKBUDDY_AI_APP_CLI" ] || return 1
3267
+ printf '%s\n' "$WORKBUDDY_AI_APP_CLI"
3268
+ }
3269
+
3270
+ aime_acp_package_spec() {
3271
+ local default_spec registry spec
3272
+ spec="${AIME_ACP_PKG:-}"
3273
+ default_spec="$(aime_acp_default_package_spec)"
3274
+ registry="${AIME_ACP_REGISTRY:-https://bnpm.byted.org}"
3275
+ case "$spec" in
3276
+ @*/aime-acp@?*)
3277
+ if [ "${registry%/}" = 'https://bnpm.byted.org' ]; then
3278
+ printf '%s\n' "$spec"
3279
+ else
3280
+ printf '%s\n' "$default_spec"
3281
+ fi
3282
+ ;;
3283
+ *.tgz)
3284
+ if [ -f "$spec" ]; then
3285
+ printf '%s\n' "$spec"
3286
+ else
3287
+ printf '%s\n' "$default_spec"
3288
+ fi
3289
+ ;;
3290
+ *) printf '%s\n' "$default_spec" ;;
3291
+ esac
3292
+ }
3293
+
3294
+ aime_acp_default_package_spec() {
3295
+ printf '%s\n' "${AAMP_TASK_DEFAULT_AIME_ACP_PKG:-@tengchengwei/aime-acp@0.1.1-dev.1}"
3296
+ }
3297
+
3298
+ aime_acp_local_package_name() {
3299
+ local spec="$1"
3300
+ tar -xOzf "$spec" package/package.json 2>/dev/null | node -e '
3301
+ let raw = "";
3302
+ process.stdin.setEncoding("utf8");
3303
+ process.stdin.on("data", (chunk) => { raw += chunk; });
3304
+ process.stdin.on("end", () => {
3305
+ try {
3306
+ const value = JSON.parse(raw);
3307
+ if (typeof value.name !== "string" || !/^@[^/]+\/aime-acp$/.test(value.name)) process.exit(1);
3308
+ process.stdout.write(value.name);
3309
+ } catch {
3310
+ process.exit(1);
3311
+ }
3312
+ });
3313
+ '
3314
+ }
3315
+
3316
+ aime_acp_package_name() {
3317
+ local local_name spec
3318
+ spec="$(aime_acp_package_spec)"
3319
+ case "$spec" in
3320
+ *.tgz)
3321
+ local_name="$(aime_acp_local_package_name "$spec" || true)"
3322
+ if [ -n "$local_name" ]; then
3323
+ printf '%s\n' "$local_name"
3324
+ return 0
3325
+ fi
3326
+ spec="$(aime_acp_default_package_spec)"
3327
+ ;;
3328
+ esac
3329
+ case "$spec" in
3330
+ @*/aime-acp@?*) printf '%s\n' "${spec%@*}" ;;
3331
+ *) return 1 ;;
3332
+ esac
3333
+ }
3334
+
3335
+ aime_acp_package_version() {
3336
+ local spec
3337
+ spec="$(aime_acp_package_spec)"
3338
+ case "$spec" in
3339
+ *.tgz) return 1 ;;
3340
+ *@?*) printf '%s\n' "${spec##*@}" ;;
3341
+ *) return 1 ;;
3342
+ esac
3343
+ }
3344
+
3345
+ aime_acp_package_spec_is_supported() {
3346
+ case "$(aime_acp_package_spec)" in
3347
+ *.tgz) return 0 ;;
3348
+ @*/aime-acp@?*) return 0 ;;
3349
+ *) return 1 ;;
3350
+ esac
3351
+ }
3352
+
3353
+ aime_acp_registry() {
3354
+ printf '%s\n' 'https://bnpm.byted.org'
3355
+ }
3356
+
3357
+ aime_acp_site() {
3358
+ printf '%s\n' 'cn'
3359
+ }
3360
+
3361
+ aime_acp_cli_path() {
3362
+ printf '%s/dist/bin.js\n' "$(aime_acp_package_dir)"
3363
+ }
3364
+
3365
+ remove_legacy_aime_acp() {
3366
+ local legacy_dir legacy_bin canonical_dir resolved_bin
3367
+ legacy_dir="$NPM_GLOBAL_PREFIX/lib/node_modules/aime-acp"
3368
+ legacy_bin="$NPM_GLOBAL_PREFIX/bin/aime-acp"
3369
+ canonical_dir="$(aime_acp_package_dir)"
3370
+ canonical_dir="$(node -e '
3371
+ const fs = require("node:fs");
3372
+ try { process.stdout.write(fs.realpathSync(process.argv[1])); } catch {}
3373
+ ' "$canonical_dir")"
3374
+ if [ -e "$legacy_dir" ] || [ -L "$legacy_dir" ]; then
3375
+ rm -rf -- "$legacy_dir"
3376
+ fi
3377
+ if [ -e "$legacy_bin" ] || [ -L "$legacy_bin" ]; then
3378
+ resolved_bin="$(node -e '
3379
+ const fs = require("node:fs");
3380
+ const path = require("node:path");
3381
+ try { process.stdout.write(fs.realpathSync(process.argv[1])); } catch {}
3382
+ ' "$legacy_bin")"
3383
+ if [ -n "$canonical_dir" ] && [ -n "$resolved_bin" ]; then
3384
+ case "$resolved_bin" in
3385
+ "$canonical_dir"/*) return 0 ;;
3386
+ esac
3387
+ fi
3388
+ rm -f -- "$legacy_bin"
3389
+ fi
3390
+ }
3391
+
3392
+ aime_acp_package_dir() {
3393
+ printf '%s/lib/node_modules/%s\n' "$NPM_GLOBAL_PREFIX" "$(aime_acp_package_name)"
3394
+ }
3395
+
3396
+ aime_acp_installed_version() {
3397
+ local package_json
3398
+ package_json="$(aime_acp_package_dir)/package.json"
3399
+ [ -r "$package_json" ] || return 1
3400
+ node -e '
3401
+ const fs = require("node:fs");
3402
+ const value = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
3403
+ if (typeof value.version !== "string") process.exit(1);
3404
+ process.stdout.write(value.version);
3405
+ ' "$package_json"
3406
+ }
3407
+
3408
+ aime_acp_installed_name() {
3409
+ local package_json
3410
+ package_json="$(aime_acp_package_dir)/package.json"
3411
+ [ -r "$package_json" ] || return 1
3412
+ node -e '
3413
+ const fs = require("node:fs");
3414
+ const value = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
3415
+ if (typeof value.name !== "string") process.exit(1);
3416
+ process.stdout.write(value.name);
3417
+ ' "$package_json"
3418
+ }
3419
+
3420
+ aime_acp_install_is_valid() {
3421
+ local installed_version installed_name
3422
+ [ -x "$(aime_acp_cli_path)" ] || return 1
3423
+ installed_name="$(aime_acp_installed_name || true)"
3424
+ [ "$installed_name" = "$(aime_acp_package_name)" ] || return 1
3425
+ installed_version="$(aime_acp_installed_version || true)"
3426
+ [ -n "$installed_version" ] || return 1
3427
+ case "$(aime_acp_package_spec)" in
3428
+ *.tgz) return 0 ;;
3429
+ *) [ "$installed_version" = "$(aime_acp_package_version)" ] ;;
3430
+ esac
3431
+ }
3432
+
3433
+ aime_acp_install_is_current() {
3434
+ case "$(aime_acp_package_spec)" in
3435
+ *.tgz) return 1 ;;
3436
+ *) aime_acp_install_is_valid ;;
3437
+ esac
3438
+ }
3439
+
3440
+ ensure_aime_acp_cli() {
3441
+ aime_acp_package_spec_is_supported \
3442
+ || agent_fail "AIME_ACP_PACKAGE_INVALID: AIME ACP must use a scoped BNPM package or a local tgz."
3443
+ remove_legacy_aime_acp
3444
+ if aime_acp_install_is_current; then
3445
+ return 0
3446
+ fi
3447
+ agent_log "正在安装固定版本 AIME ACP 到隔离运行环境。"
3448
+ npm_install_global_from_registry "$(aime_acp_registry)" "$(aime_acp_package_spec)" \
3449
+ || agent_fail "AIME_ACP_INSTALL_FAILED: AIME ACP installation failed. Confirm company network access and retry."
3450
+ aime_acp_install_is_valid \
3451
+ || agent_fail "AIME_ACP_INSTALL_INVALID: AIME ACP installation validation failed."
3452
+ }
3453
+
2681
3454
  resolve_cursor_cli_for_acp() {
2682
3455
  command -v cursor
2683
3456
  }
@@ -2720,6 +3493,50 @@ ensure_agent_cli() {
2720
3493
  return 0
2721
3494
  fi
2722
3495
 
3496
+ if [ "$AGENT" = "traex" ]; then
3497
+ find_traex_cli >/dev/null 2>&1 || agent_fail "未检测到 traex CLI。请先安装 Trae CLI Next(内部版)后重新运行脚本。"
3498
+ return 0
3499
+ fi
3500
+
3501
+ if [ "$AGENT" = "traecli" ]; then
3502
+ local traecode_status
3503
+ set +e
3504
+ find_traecode_cli >/dev/null 2>&1
3505
+ traecode_status=$?
3506
+ set -e
3507
+ [ "$traecode_status" -ne 2 ] || agent_fail "检测到的 traecli 属于 Trae CLI(内部版),不能作为 TraeCode CLI 启动。"
3508
+ [ "$traecode_status" -eq 0 ] || agent_fail "未检测到 TraeCode CLI。请先安装 traecli 后重新运行脚本。"
3509
+ return 0
3510
+ fi
3511
+
3512
+ if [ "$AGENT" = "coco" ]; then
3513
+ if find_traex_cli >/dev/null 2>&1; then return 0; fi
3514
+ if find_coco_cli >/dev/null 2>&1; then return 0; fi
3515
+ if find_traecode_cli >/dev/null 2>&1; then return 0; fi
3516
+ agent_fail "未检测到 Trae CLI(内部版)、Trae CLI Next(内部版)或 TraeCode CLI。"
3517
+ fi
3518
+
3519
+ if [ "$AGENT" = "workbuddy" ]; then
3520
+ is_macos || agent_fail "WorkBuddy 一键探测仅支持 macOS。"
3521
+ find_workbuddy_cli >/dev/null 2>&1 \
3522
+ || agent_fail "未检测到 WorkBuddy。请确认已安装到 /Applications/WorkBuddy.app 后重新运行脚本。"
3523
+ return 0
3524
+ fi
3525
+
3526
+ if [ "$AGENT" = "workbuddy_ai" ]; then
3527
+ is_macos || agent_fail "WorkBuddy AI 一键探测仅支持 macOS。"
3528
+ find_workbuddy_ai_cli >/dev/null 2>&1 \
3529
+ || agent_fail "未检测到 WorkBuddy AI CLI:${WORKBUDDY_AI_APP_CLI}。请确认该文件存在且可执行后重新运行脚本。"
3530
+ return 0
3531
+ fi
3532
+
3533
+ if [ "$AGENT" = "aime" ]; then
3534
+ aime_internal_network_reachable \
3535
+ || agent_fail "AIME 仅在公司内网可用;当前无法 ping 通 aime.bytedance.net。"
3536
+ ensure_aime_acp_cli
3537
+ return 0
3538
+ fi
3539
+
2723
3540
  command -v "$AGENT" >/dev/null 2>&1 || agent_fail "未检测到 $AGENT CLI。请先安装后重新运行脚本。"
2724
3541
  }
2725
3542
 
@@ -2867,6 +3684,68 @@ resolve_latest_codex_cli_version() {
2867
3684
  --prefix "$NPM_GLOBAL_PREFIX" 2>>"$ONE_CLICK_LOG" | tail -n 1 | tr -d '[:space:]'
2868
3685
  }
2869
3686
 
3687
+ codex_update_cache_latest() {
3688
+ local current_version="$1"
3689
+ [ -n "${CODEX_UPDATE_CACHE_FILE:-}" ] || return 1
3690
+ [ -n "$current_version" ] || return 1
3691
+ [ -f "$CODEX_UPDATE_CACHE_FILE" ] || return 1
3692
+ CACHE_FILE="$CODEX_UPDATE_CACHE_FILE" \
3693
+ TTL="${CODEX_UPDATE_CACHE_TTL_SECONDS:-86400}" \
3694
+ CURRENT_VERSION="$current_version" \
3695
+ NPM_PACKAGE="${CODEX_NPM_PACKAGE:-@openai/codex}" \
3696
+ NPM_REGISTRY_VALUE="${NPM_REGISTRY:-https://registry.npmjs.org/}" \
3697
+ node -e '
3698
+ const fs = require("fs");
3699
+ let data;
3700
+ try { data = JSON.parse(fs.readFileSync(process.env.CACHE_FILE, "utf8")); } catch { process.exit(1); }
3701
+ const checkedAt = Number(data.checked_at || 0);
3702
+ const ttl = Number(process.env.TTL || 86400);
3703
+ const age = Math.floor(Date.now() / 1000) - checkedAt;
3704
+ if (data.version !== 1 || typeof data.checked_at !== "number" || !Number.isFinite(checkedAt) || checkedAt <= 0) process.exit(1);
3705
+ if (!Number.isFinite(ttl) || ttl <= 0 || age < 0 || age >= ttl) process.exit(1);
3706
+ if (typeof data.current_version !== "string" || typeof data.latest_version !== "string") process.exit(1);
3707
+ if (typeof data.package !== "string" || typeof data.registry !== "string") process.exit(1);
3708
+ if (String(data.current_version || "") !== String(process.env.CURRENT_VERSION || "")) process.exit(1);
3709
+ if (String(data.package || "") !== String(process.env.NPM_PACKAGE || "")) process.exit(1);
3710
+ if (String(data.registry || "") !== String(process.env.NPM_REGISTRY_VALUE || "")) process.exit(1);
3711
+ const latest = String(data.latest_version || "").trim();
3712
+ if (!/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(latest)) process.exit(1);
3713
+ process.stdout.write(latest);
3714
+ ' 2>>"${ONE_CLICK_LOG:-/dev/null}"
3715
+ }
3716
+
3717
+ write_codex_update_cache() {
3718
+ local current_version="$1"
3719
+ local latest_version="$2"
3720
+ [ -n "${CODEX_UPDATE_CACHE_FILE:-}" ] || return 0
3721
+ [ -n "$current_version" ] && [ -n "$latest_version" ] || return 0
3722
+ mkdir -p "$(dirname "$CODEX_UPDATE_CACHE_FILE")"
3723
+ CACHE_FILE="$CODEX_UPDATE_CACHE_FILE" \
3724
+ CURRENT_VERSION="$current_version" \
3725
+ LATEST_VERSION="$latest_version" \
3726
+ NPM_PACKAGE="${CODEX_NPM_PACKAGE:-@openai/codex}" \
3727
+ NPM_REGISTRY_VALUE="${NPM_REGISTRY:-https://registry.npmjs.org/}" \
3728
+ node -e '
3729
+ const fs = require("fs");
3730
+ const file = process.env.CACHE_FILE;
3731
+ const temporary = `${file}.tmp.${process.pid}`;
3732
+ const payload = {
3733
+ version: 1,
3734
+ checked_at: Math.floor(Date.now() / 1000),
3735
+ current_version: process.env.CURRENT_VERSION || "",
3736
+ latest_version: process.env.LATEST_VERSION || "",
3737
+ package: process.env.NPM_PACKAGE || "",
3738
+ registry: process.env.NPM_REGISTRY_VALUE || "",
3739
+ };
3740
+ try {
3741
+ fs.writeFileSync(temporary, JSON.stringify(payload, null, 2) + "\n", { mode: 0o600 });
3742
+ fs.renameSync(temporary, file);
3743
+ } finally {
3744
+ try { fs.unlinkSync(temporary); } catch {}
3745
+ }
3746
+ ' 2>>"${ONE_CLICK_LOG:-/dev/null}" || true
3747
+ }
3748
+
2870
3749
  codex_cli_update_available() {
2871
3750
  CODEX_CURRENT_VERSION="$1" CODEX_LATEST_VERSION="$2" node -e '
2872
3751
  function parse(value) {
@@ -3005,7 +3884,13 @@ ensure_codex_cli_updated() {
3005
3884
  codex_bin="$(resolve_codex_cli_for_acp || true)"
3006
3885
  [ -n "$codex_bin" ] || return 0
3007
3886
  version_before="$(codex_cli_version_number "$codex_bin" || true)"
3008
- latest_version="$(resolve_latest_codex_cli_version || true)"
3887
+ latest_version="$(codex_update_cache_latest "$version_before" || true)"
3888
+ if [ -n "$latest_version" ]; then
3889
+ agent_detail "Codex CLI update check cache is fresh: current=${version_before:-unknown} latest=$latest_version"
3890
+ else
3891
+ latest_version="$(resolve_latest_codex_cli_version || true)"
3892
+ write_codex_update_cache "$version_before" "$latest_version"
3893
+ fi
3009
3894
  version_line="当前 Codex CLI 版本是:${version_before:-未知},最新版本是:${latest_version:-获取失败}"
3010
3895
  write_one_click_log "[aamp-one-click] $version_line"
3011
3896
  agent_detail "checking Codex CLI update: path=$codex_bin version=${version_before:-unknown}"
@@ -3016,7 +3901,6 @@ ensure_codex_cli_updated() {
3016
3901
  comparison_status=$?
3017
3902
  fi
3018
3903
  if [ "$comparison_status" -eq 1 ]; then
3019
- printf '%s\n' "$version_line"
3020
3904
  agent_detail "Codex CLI is already current: current=${version_before:-unknown} latest=${latest_version:-unknown}"
3021
3905
  return 0
3022
3906
  fi
@@ -3105,6 +3989,142 @@ run_codex_login() {
3105
3989
  return "$status"
3106
3990
  }
3107
3991
 
3992
+ run_quiet_command_with_timeout() {
3993
+ local timeout_seconds="$1"
3994
+ shift
3995
+ node - "$timeout_seconds" "$@" <<'NODE'
3996
+ const { spawn } = require('node:child_process');
3997
+
3998
+ const timeoutSeconds = Number(process.argv[2] || '10');
3999
+ const timeoutMs = Math.max(1, Number.isFinite(timeoutSeconds) ? timeoutSeconds : 10) * 1000;
4000
+ const [command, ...args] = process.argv.slice(3);
4001
+ if (!command) process.exit(127);
4002
+
4003
+ let finished = false;
4004
+ let timedOut = false;
4005
+ const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'ignore'] });
4006
+ const timer = setTimeout(() => {
4007
+ timedOut = true;
4008
+ child.kill('SIGTERM');
4009
+ setTimeout(() => child.kill('SIGKILL'), 1000).unref();
4010
+ }, timeoutMs);
4011
+
4012
+ child.once('error', (error) => {
4013
+ if (finished) return;
4014
+ finished = true;
4015
+ clearTimeout(timer);
4016
+ process.exit(error && error.code === 'ENOENT' ? 127 : 1);
4017
+ });
4018
+
4019
+ child.once('exit', (code, signal) => {
4020
+ if (finished) return;
4021
+ finished = true;
4022
+ clearTimeout(timer);
4023
+ if (timedOut) process.exit(124);
4024
+ if (typeof code === 'number') process.exit(code);
4025
+ process.exit(signal ? 1 : 0);
4026
+ });
4027
+ NODE
4028
+ }
4029
+
4030
+ run_trae_login_status_for_bin() {
4031
+ local trae_bin="$1"
4032
+ run_quiet_command_with_timeout "$AAMP_TRAE_LOGIN_STATUS_TIMEOUT_SECONDS" "$trae_bin" login status
4033
+ }
4034
+
4035
+ trae_login_status_timeout_message() {
4036
+ printf '%s' "traex login status 超时。请手动执行 'traex login status' 检查登录状态后重新运行脚本。"
4037
+ }
4038
+
4039
+ run_traex_login_status() {
4040
+ local trae_bin status
4041
+ trae_bin="$(find_traex_cli || true)"
4042
+ [ -n "$trae_bin" ] || return 127
4043
+ TRAE_CLI_BIN="$trae_bin"
4044
+ agent_detail "checking Trae CLI login status: $trae_bin"
4045
+ if run_trae_login_status_for_bin "$trae_bin"; then
4046
+ return 0
4047
+ else
4048
+ status=$?
4049
+ fi
4050
+ if [ "$status" -eq 124 ]; then
4051
+ agent_log "Trae CLI login status timed out after ${AAMP_TRAE_LOGIN_STATUS_TIMEOUT_SECONDS}s: $trae_bin"
4052
+ fi
4053
+ return "$status"
4054
+ }
4055
+
4056
+ run_traex_login() {
4057
+ local trae_bin
4058
+ trae_bin="$(resolve_trae_cli || true)"
4059
+ [ -n "$trae_bin" ] || return 127
4060
+ "$trae_bin" login
4061
+ }
4062
+
4063
+ trae_cli_is_traex() {
4064
+ [ -n "$TRAE_CLI_BIN" ] && [ "${TRAE_CLI_BIN##*/}" = "traex" ]
4065
+ }
4066
+
4067
+ confirm_trae_upgrade() {
4068
+ local answer
4069
+ printf '\n检测到 Trae CLI(内部版),飞书任务 Agent 暂不支持使用该版本建立连接。\n' >&2
4070
+ printf '建议升级到 Trae CLI Next(内部版)(traex)后继续。\n' >&2
4071
+ printf '升级过程中可自行选择是否保留 Trae CLI(内部版)。\n' >&2
4072
+ answer="$(read_tty_line "是否现在升级到 traex?[y/N] ")"
4073
+ case "$answer" in
4074
+ y|Y|yes|YES|Yes) return 0 ;;
4075
+ *) return 1 ;;
4076
+ esac
4077
+ }
4078
+
4079
+ run_traex_installer() {
4080
+ agent_log "正在安装/升级 traex..."
4081
+ curl -fsSL "$TRAEX_INSTALLER_URL" | sh
4082
+ }
4083
+
4084
+ maybe_upgrade_legacy_trae_cli() {
4085
+ if trae_cli_is_traex; then
4086
+ AGENT="traex"
4087
+ return 0
4088
+ fi
4089
+
4090
+ if confirm_trae_upgrade; then
4091
+ run_traex_installer || agent_fail "traex 安装失败。请手动执行:curl -fsSL $TRAEX_INSTALLER_URL | sh"
4092
+ hash -r 2>/dev/null || true
4093
+ TRAE_CLI_BIN=""
4094
+ find_traex_cli >/dev/null 2>&1 || agent_fail "traex 安装后仍未检测到。请确认 traex 在 PATH 中,然后重新运行脚本。"
4095
+ AGENT="traex"
4096
+ agent_log "已切换为 traex,继续启动。"
4097
+ return 0
4098
+ fi
4099
+
4100
+ AGENT_PREPARE_CANCELLED="true"
4101
+ AGENT_PREPARE_CANCEL_REASON="用户取消升级。飞书任务 Agent 暂不支持使用 Trae CLI(内部版)建立连接,本次未启动飞书任务连接。如需继续,请先安装 Trae CLI Next(内部版):curl -fsSL $TRAEX_INSTALLER_URL | sh"
4102
+ agent_log "已取消升级,本次未启动飞书任务连接。"
4103
+ }
4104
+
4105
+ ensure_traex_login() {
4106
+ local trae_login_status
4107
+ set +e
4108
+ run_traex_login_status
4109
+ trae_login_status=$?
4110
+ set -e
4111
+ if [ "$trae_login_status" -eq 124 ]; then
4112
+ agent_fail "$(trae_login_status_timeout_message)"
4113
+ fi
4114
+ if [ "$trae_login_status" -ne 0 ]; then
4115
+ agent_log "Trae CLI Next(内部版)未登录,正在启动登录流程。"
4116
+ run_traex_login || agent_fail "Trae CLI Next(内部版)登录失败。请先执行 'traex login' 完成登录后重新运行脚本。"
4117
+ set +e
4118
+ run_traex_login_status
4119
+ trae_login_status=$?
4120
+ set -e
4121
+ if [ "$trae_login_status" -eq 124 ]; then
4122
+ agent_fail "$(trae_login_status_timeout_message)"
4123
+ fi
4124
+ [ "$trae_login_status" -eq 0 ] || agent_fail "Trae CLI Next(内部版)仍未登录。请先执行 'traex login' 完成登录后重新运行脚本。"
4125
+ fi
4126
+ }
4127
+
3108
4128
  print_cursor_gatekeeper_help() {
3109
4129
  local cursor_bin
3110
4130
  local cursor_real
@@ -3366,6 +4386,144 @@ ensure_codem_provider_ready() {
3366
4386
  return "$status"
3367
4387
  }
3368
4388
 
4389
+ traecode_readiness_helper_path() {
4390
+ if [ -n "$AAMP_TRAECODE_READINESS_HELPER" ]; then printf '%s\n' "$AAMP_TRAECODE_READINESS_HELPER"; return 0; fi
4391
+ local helper
4392
+ helper="$(task_agent_global_package_dir)/bin/traecode-readiness.mjs"
4393
+ [ -r "$helper" ] || return 1
4394
+ printf '%s\n' "$helper"
4395
+ }
4396
+
4397
+ run_traecode_check() {
4398
+ local action="$1" helper traecode_bin
4399
+ helper="$(traecode_readiness_helper_path)" || return 127
4400
+ traecode_bin="$(find_traecode_cli)" || return $?
4401
+ node "$helper" "$action" "$traecode_bin" "$AAMP_TRAECODE_CHECK_TIMEOUT_SECONDS"
4402
+ }
4403
+
4404
+ confirm_traecode_update() {
4405
+ local answer tty_path
4406
+ tty_path="${AAMP_TRAECODE_UPDATE_TTY:-/dev/tty}"
4407
+ if ! exec 6<>"$tty_path"; then return 2; fi
4408
+ printf '%s' '当前 TraeCode CLI 版本较旧,不支持 ACP。是否执行 `traecli update` 升级?[y/N] ' >&6
4409
+ if ! IFS= read -r answer <&6; then exec 6>&-; return 2; fi
4410
+ exec 6>&-
4411
+ case "$answer" in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac
4412
+ }
4413
+
4414
+ run_traecode_update() {
4415
+ local traecode_bin="$1"
4416
+ [ -n "$traecode_bin" ] || return 127
4417
+ "$traecode_bin" update
4418
+ }
4419
+
4420
+ ensure_traecode_acp() {
4421
+ local probe_status confirm_status update_bin
4422
+ set +e; run_traecode_check probe-acp >/dev/null; probe_status=$?; set -e
4423
+ [ "$probe_status" -eq 0 ] && return 0
4424
+ [ "$probe_status" -ne 124 ] || agent_fail "TraeCode CLI ACP 能力检查超时,请稍后重试。"
4425
+ [ "$probe_status" -eq 3 ] || agent_fail "无法检查 TraeCode CLI 的 ACP 能力。"
4426
+
4427
+ set +e; confirm_traecode_update; confirm_status=$?; set -e
4428
+ if [ "$confirm_status" -eq 2 ]; then
4429
+ agent_fail "当前 TraeCode CLI 需要升级。请在交互式终端执行 'traecli update' 后重试。"
4430
+ fi
4431
+ if [ "$confirm_status" -ne 0 ]; then
4432
+ AGENT_PREPARE_CANCELLED="true"
4433
+ AGENT_PREPARE_CANCEL_REASON="用户取消升级 TraeCode CLI,本次未启动飞书任务连接。"
4434
+ return 0
4435
+ fi
4436
+
4437
+ update_bin="$(find_traecode_cli)" || agent_fail "升级前无法重新定位 TraeCode CLI,请手动执行 'traecli update' 后重试。"
4438
+ run_traecode_update "$update_bin" || agent_fail "TraeCode CLI 升级失败,请查看上方升级输出,或手动执行 'traecli update' 后重试。"
4439
+ hash -r 2>/dev/null || true
4440
+ TRAECODE_CLI_BIN=""
4441
+ find_traecode_cli >/dev/null 2>&1 || agent_fail "升级后仍未检测到 TraeCode CLI,请检查 PATH 后重试。"
4442
+ set +e; run_traecode_check probe-acp >/dev/null; probe_status=$?; set -e
4443
+ [ "$probe_status" -eq 0 ] || agent_fail "TraeCode CLI 升级后仍不支持 ACP,请确认升级结果后重试。"
4444
+ }
4445
+
4446
+ ensure_traecode_doctor() {
4447
+ local output doctor_status
4448
+ set +e; output="$(run_traecode_check doctor 2>&1)"; doctor_status=$?; set -e
4449
+ case "$doctor_status" in
4450
+ 0) [ -z "$output" ] || printf 'TraeCode CLI 诊断警告:%s\n' "$output" ;;
4451
+ 10) agent_fail "TraeCode CLI 尚未准备好。请打开 TraeCode CLI,按提示完成登录,并使用 /model 选择模型后重试。" ;;
4452
+ 11) agent_fail "TraeCode CLI 诊断未通过:${output:-未知错误}" ;;
4453
+ 65) agent_fail "TraeCode CLI 诊断结果无法解析,请执行 'traecli doctor --json' 检查后重试。" ;;
4454
+ 124) agent_fail "TraeCode CLI 诊断超时,请稍后重试。" ;;
4455
+ *) agent_fail "TraeCode CLI 诊断执行失败:${output:-请执行 'traecli doctor --json' 检查。}" ;;
4456
+ esac
4457
+ }
4458
+
4459
+ ensure_traecode_ready() {
4460
+ ensure_traecode_acp
4461
+ [ "$AGENT_PREPARE_CANCELLED" = "true" ] && return 0
4462
+ ensure_traecode_doctor
4463
+ }
4464
+
4465
+ run_aime_auth_status() {
4466
+ "$(aime_acp_cli_path)" auth status --site "$(aime_acp_site)" --json
4467
+ }
4468
+
4469
+ run_aime_auth_login() {
4470
+ "$(aime_acp_cli_path)" auth login --site "$(aime_acp_site)"
4471
+ }
4472
+
4473
+ run_aime_doctor() {
4474
+ "$(aime_acp_cli_path)" doctor --site "$(aime_acp_site)" --json
4475
+ }
4476
+
4477
+ ensure_aime_ready() {
4478
+ local auth_output auth_status auth_kind login_status doctor_status
4479
+ set +e
4480
+ auth_output="$(run_aime_auth_status)"
4481
+ auth_status=$?
4482
+ set -e
4483
+ auth_kind="$(printf '%s' "$auth_output" | node -e '
4484
+ let input = "";
4485
+ process.stdin.setEncoding("utf8");
4486
+ process.stdin.on("data", (chunk) => { input += chunk; });
4487
+ process.stdin.on("end", () => {
4488
+ try {
4489
+ const value = JSON.parse(input.trim());
4490
+ if (value?.ok === true && value?.status === "authenticated") process.stdout.write("authenticated");
4491
+ else if (value?.ok === true && value?.status === "unauthenticated") process.stdout.write("unauthenticated");
4492
+ else process.stdout.write("error");
4493
+ } catch {
4494
+ process.stdout.write("error");
4495
+ }
4496
+ });
4497
+ ')"
4498
+ if [ "$auth_status" -eq 0 ] && [ "$auth_kind" = "authenticated" ]; then
4499
+ agent_detail "AIME managed-user authentication is ready"
4500
+ elif [ "$auth_status" -eq 1 ] && [ "$auth_kind" = "unauthenticated" ]; then
4501
+ agent_log "AIME 尚未登录,正在启动独立登录流程。"
4502
+ set +e
4503
+ run_aime_auth_login
4504
+ login_status=$?
4505
+ set -e
4506
+ case "$login_status" in
4507
+ 0) ;;
4508
+ 2)
4509
+ agent_fail "AIME 登录仍在等待完成。请继续完成浏览器授权,或重新执行 'aime-acp auth login --site cn' 后重试。"
4510
+ ;;
4511
+ *)
4512
+ agent_fail "AIME 登录失败。请执行 'aime-acp auth login --site cn' 后重试。"
4513
+ ;;
4514
+ esac
4515
+ else
4516
+ agent_fail "AIME 认证状态检查失败。请执行 'aime-acp auth status --site cn --json' 查看安全诊断。"
4517
+ fi
4518
+
4519
+ set +e
4520
+ run_aime_doctor >/dev/null
4521
+ doctor_status=$?
4522
+ set -e
4523
+ [ "$doctor_status" -eq 0 ] \
4524
+ || agent_fail "AIME doctor 未通过。请确认公司内网与账号状态,并执行 'aime-acp doctor --site cn --json' 查看安全诊断。"
4525
+ }
4526
+
3369
4527
  ensure_agent_login() {
3370
4528
  case "$AGENT" in
3371
4529
  codex)
@@ -3386,6 +4544,35 @@ ensure_agent_login() {
3386
4544
  run_cursor_login_status || agent_fail "cursor CLI 仍未登录。请先执行 'cursor login' 或 'agent login' 完成登录后重新运行脚本。"
3387
4545
  fi
3388
4546
  ;;
4547
+ traex)
4548
+ ensure_traex_login
4549
+ ;;
4550
+ traecli)
4551
+ ensure_traecode_ready
4552
+ ;;
4553
+ coco)
4554
+ if find_traex_cli >/dev/null 2>&1; then
4555
+ AGENT="traex"
4556
+ ensure_traex_login
4557
+ elif find_coco_cli >/dev/null 2>&1; then
4558
+ maybe_upgrade_legacy_trae_cli
4559
+ if [ "$AGENT" = "traex" ]; then ensure_traex_login; fi
4560
+ elif find_traecode_cli >/dev/null 2>&1; then
4561
+ AGENT="traecli"
4562
+ ensure_traecode_ready
4563
+ else
4564
+ agent_fail "未检测到 Trae CLI(内部版)、Trae CLI Next(内部版)或 TraeCode CLI。"
4565
+ fi
4566
+ ;;
4567
+ workbuddy)
4568
+ agent_detail "WorkBuddy authentication is managed by the desktop app"
4569
+ ;;
4570
+ workbuddy_ai)
4571
+ agent_detail "WorkBuddy AI authentication is managed by the desktop app"
4572
+ ;;
4573
+ aime)
4574
+ ensure_aime_ready
4575
+ ;;
3389
4576
  codem)
3390
4577
  ensure_codem_local_bin_on_path
3391
4578
  codem --version >/dev/null
@@ -3467,8 +4654,69 @@ resolve_codex_cli_for_acp() {
3467
4654
  return 1
3468
4655
  }
3469
4656
 
4657
+ acp_command_word() {
4658
+ local value="$1" single_quote_escape
4659
+ case "$value" in *$'\n'*|*$'\r'*) return 1 ;; esac
4660
+ single_quote_escape="'\"'\"'"
4661
+ value="${value//\'/$single_quote_escape}"
4662
+ printf "'%s'" "$value"
4663
+ }
4664
+
3470
4665
  build_acp_agent_command() {
3471
4666
  ACP_AGENT_COMMAND="$AGENT"
4667
+ if [ "$AGENT" = "workbuddy" ]; then
4668
+ local workbuddy_bin workbuddy_config_dir_word
4669
+ workbuddy_bin="$(find_workbuddy_cli)" \
4670
+ || agent_fail "WorkBuddy 不可用。请确认已安装到 /Applications/WorkBuddy.app。"
4671
+ workbuddy_config_dir_word="$(acp_command_word "$HOME/.workbuddy")" \
4672
+ || agent_fail "WorkBuddy 配置目录包含不受支持的换行符。"
4673
+ ACP_AGENT_COMMAND="env CODEBUDDY_CONFIG_DIR=$workbuddy_config_dir_word CODEBUDDY_SKIP_BUILTIN_MARKETPLACE=1 $workbuddy_bin --acp"
4674
+ agent_detail "using native WorkBuddy ACP command: $ACP_AGENT_COMMAND"
4675
+ return 0
4676
+ fi
4677
+
4678
+ if [ "$AGENT" = "workbuddy_ai" ]; then
4679
+ local workbuddy_ai_bin workbuddy_ai_word workbuddy_ai_config_dir_word
4680
+ workbuddy_ai_bin="$(find_workbuddy_ai_cli)" \
4681
+ || agent_fail "WorkBuddy AI CLI 不可用:${WORKBUDDY_AI_APP_CLI}。请确认该文件存在且可执行。"
4682
+ workbuddy_ai_word="$(acp_command_word "$workbuddy_ai_bin")" \
4683
+ || agent_fail "WorkBuddy AI 路径包含不受支持的换行符。"
4684
+ workbuddy_ai_config_dir_word="$(acp_command_word "$HOME/.workbuddy-ai")" \
4685
+ || agent_fail "WorkBuddy AI 配置目录包含不受支持的换行符。"
4686
+ ACP_AGENT_COMMAND="env CODEBUDDY_CONFIG_DIR=$workbuddy_ai_config_dir_word CODEBUDDY_SKIP_BUILTIN_MARKETPLACE=1 $workbuddy_ai_word --acp"
4687
+ agent_detail "using native WorkBuddy AI ACP command: $ACP_AGENT_COMMAND"
4688
+ return 0
4689
+ fi
4690
+
4691
+ if [ "$AGENT" = "aime" ]; then
4692
+ local aime_bin aime_word
4693
+ aime_bin="$(aime_acp_cli_path)"
4694
+ [ -x "$aime_bin" ] || agent_fail "AIME_ACP_UNAVAILABLE: AIME ACP executable is unavailable after preparation."
4695
+ aime_word="$(acp_command_word "$aime_bin")" \
4696
+ || agent_fail "AIME_ACP_PATH_INVALID: AIME ACP executable path is invalid."
4697
+ ACP_AGENT_COMMAND="$aime_word --site $(aime_acp_site)"
4698
+ agent_detail "AIME ACP configured for site $(aime_acp_site)"
4699
+ return 0
4700
+ fi
4701
+
4702
+ if [ "$AGENT" = "traecli" ]; then
4703
+ local traecode_bin traecode_word
4704
+ traecode_bin="$(find_traecode_cli)" || agent_fail "TraeCode CLI 不可用,无法启动 ACP 服务。"
4705
+ traecode_word="$(acp_command_word "$traecode_bin")" \
4706
+ || agent_fail "TraeCode CLI 路径包含不受支持的换行符。"
4707
+ ACP_AGENT_COMMAND="$traecode_word acp serve"
4708
+ agent_detail "using native TraeCode ACP command: $ACP_AGENT_COMMAND"
4709
+ return 0
4710
+ fi
4711
+
4712
+ if [ "$AGENT" = "coco" ] || [ "$AGENT" = "traex" ]; then
4713
+ local trae_bin
4714
+ trae_bin="$(find_traex_cli)" || agent_fail "Trae CLI Next(内部版)(traex)不可用,无法启动飞书任务所需的 ACP 服务。"
4715
+ ACP_AGENT_COMMAND="$trae_bin acp serve"
4716
+ agent_detail "using native Trae ACP command: $ACP_AGENT_COMMAND"
4717
+ return 0
4718
+ fi
4719
+
3472
4720
  if [ "$AGENT" != "codex" ]; then
3473
4721
  return 0
3474
4722
  fi
@@ -3689,6 +4937,22 @@ run_internal_discover_agents() {
3689
4937
  if agent_cli_detected cursor; then
3690
4938
  agents+=("cursor")
3691
4939
  fi
4940
+ if agent_cli_detected traex; then
4941
+ agents+=("traex")
4942
+ elif agent_cli_detected coco; then
4943
+ agents+=("coco")
4944
+ elif agent_cli_detected traecli; then
4945
+ agents+=("traecli")
4946
+ fi
4947
+ if agent_cli_detected workbuddy; then
4948
+ agents+=("workbuddy")
4949
+ fi
4950
+ if agent_cli_detected workbuddy_ai; then
4951
+ agents+=("workbuddy_ai")
4952
+ fi
4953
+ if agent_cli_detected aime; then
4954
+ agents+=("aime")
4955
+ fi
3692
4956
  local joined=""
3693
4957
  if [ "${#agents[@]}" -gt 0 ]; then
3694
4958
  joined="$(IFS=,; printf '%s' "${agents[*]}")"
@@ -3702,47 +4966,98 @@ process.stdout.write(JSON.stringify({ agents }));
3702
4966
  prepare_internal_agent_environment() {
3703
4967
  [ -n "$AGENT" ] || agent_fail "internal Agent preparation requires --agent"
3704
4968
  validate_agent_name "$AGENT"
3705
- source_lark_env
4969
+ ensure_agent_selection_available "$AGENT"
4970
+ load_agent_metadata
4971
+ if [ "$AGENT_EXECUTION_LOCATION" = "local" ]; then
4972
+ source_lark_env
4973
+ fi
3706
4974
  ensure_agent_cli
3707
4975
  }
3708
4976
 
3709
4977
  run_internal_register_binding() {
3710
4978
  [ -n "$AGENT" ] || agent_fail "internal Bot registration requires --agent"
3711
4979
  validate_agent_name "$AGENT"
3712
- source_lark_env
4980
+ ensure_agent_selection_available "$AGENT"
4981
+ load_agent_metadata
3713
4982
  register_feishu_app
3714
- emit_internal_result "{\"app_id\":\"$(json_escape "$APP_ID")\",\"app_secret\":\"$(json_escape "$APP_SECRET")\",\"display_name\":\"$(json_escape "$BOT_NAME")\",\"lark_cli_profile\":\"$(json_escape "$LARK_CLI_PROFILE")\"}"
4983
+ if [ "$AGENT_EXECUTION_LOCATION" = "remote" ]; then
4984
+ emit_internal_result "{\"app_id\":\"$(json_escape "$APP_ID")\",\"app_secret\":\"$(json_escape "$APP_SECRET")\",\"display_name\":\"$(json_escape "$BOT_NAME")\",\"tenant_brand\":\"$(json_escape "$APP_TENANT_BRAND")\",\"auth_mode\":\"app-secret\"}"
4985
+ else
4986
+ emit_internal_result "{\"app_id\":\"$(json_escape "$APP_ID")\",\"app_secret\":\"$(json_escape "$APP_SECRET")\",\"display_name\":\"$(json_escape "$BOT_NAME")\",\"tenant_brand\":\"$(json_escape "$APP_TENANT_BRAND")\",\"lark_cli_profile\":\"$(json_escape "$LARK_CLI_PROFILE")\",\"auth_mode\":\"lark-cli\"}"
4987
+ fi
3715
4988
  }
3716
4989
 
3717
4990
  run_internal_prepare_agent() {
3718
4991
  prepare_internal_agent_environment
3719
4992
  ensure_codex_cli_updated
3720
4993
  ensure_agent_login
4994
+ if [ "$AGENT_PREPARE_CANCELLED" = "true" ]; then
4995
+ emit_internal_result "{\"cancelled\":true,\"agent_type\":\"$(json_escape "$AGENT")\",\"reason\":\"$(json_escape "$AGENT_PREPARE_CANCEL_REASON")\"}"
4996
+ return 0
4997
+ fi
3721
4998
  maybe_mock_fail "agent-login"
3722
4999
  ensure_acpx
3723
5000
  build_acp_agent_command
3724
- emit_internal_result "{\"agent_type\":\"$(json_escape "$AGENT")\",\"acp_command\":\"$(json_escape "$ACP_AGENT_COMMAND")\",\"lark_cli_config_dir\":\"$(json_escape "${LARKSUITE_CLI_CONFIG_DIR:-}")\"}"
5001
+ if [ "$AGENT_EXECUTION_LOCATION" = "remote" ]; then
5002
+ emit_internal_result "{\"agent_type\":\"$(json_escape "$AGENT")\",\"acp_command\":\"$(json_escape "$ACP_AGENT_COMMAND")\"}"
5003
+ else
5004
+ emit_internal_result "{\"agent_type\":\"$(json_escape "$AGENT")\",\"acp_command\":\"$(json_escape "$ACP_AGENT_COMMAND")\",\"lark_cli_config_dir\":\"$(json_escape "${LARKSUITE_CLI_CONFIG_DIR:-}")\"}"
5005
+ fi
5006
+ }
5007
+
5008
+ run_internal_probe_profile() {
5009
+ AAMP_TASK_INTERNAL_BINDING_JSON=""
5010
+ IFS= read -r AAMP_TASK_INTERNAL_BINDING_JSON <&"$AAMP_TASK_INTERNAL_INPUT_FD" || true
5011
+ [ -n "$AAMP_TASK_INTERNAL_BINDING_JSON" ] || agent_fail "missing internal binding payload"
5012
+ AGENT="$(binding_json_field agent_type)"
5013
+ validate_agent_name "$AGENT"
5014
+ load_agent_metadata
5015
+ [ "$AGENT_EXECUTION_LOCATION" != "remote" ] || agent_fail "remote bindings do not use lark-cli profiles"
5016
+ APP_ID="$(binding_json_field bot.app_id)"
5017
+ LARK_CLI_PROFILE="$(binding_json_field bot.lark_cli_profile)"
5018
+ [ -n "$APP_ID" ] && [ -n "$LARK_CLI_PROFILE" ] || agent_fail "binding is missing Feishu app or profile"
5019
+ AAMP_TASK_INTERNAL_BINDING_JSON=""
5020
+ unset HTTPS_PROXY https_proxy HTTP_PROXY http_proxy ALL_PROXY all_proxy
5021
+ export LARKSUITE_CLI_CONFIG_DIR="$AAMP_LARK_CLI_CONFIG_DIR"
5022
+ unset LARK_CLI_NO_PROXY
5023
+ if ! select_lark_cli_bin_from_candidates; then
5024
+ emit_internal_result '{"ready":false}'
5025
+ return 0
5026
+ fi
5027
+ if with_lark_cli_config_lock probe_lark_cli_profile_locked "$LARK_CLI_PROFILE"; then
5028
+ emit_internal_result "{\"ready\":true,\"lark_cli_bin\":\"$(json_escape "$LARK_CLI_CMD")\",\"lark_cli_config_dir\":\"$(json_escape "${LARKSUITE_CLI_CONFIG_DIR:-}")\"}"
5029
+ else
5030
+ emit_internal_result '{"ready":false}'
5031
+ fi
3725
5032
  }
3726
5033
 
3727
5034
  run_internal_ensure_profile() {
3728
5035
  AAMP_TASK_INTERNAL_BINDING_JSON=""
3729
5036
  IFS= read -r AAMP_TASK_INTERNAL_BINDING_JSON <&"$AAMP_TASK_INTERNAL_INPUT_FD" || true
3730
5037
  [ -n "$AAMP_TASK_INTERNAL_BINDING_JSON" ] || agent_fail "missing internal binding payload"
5038
+ AGENT="$(binding_json_field agent_type)"
5039
+ validate_agent_name "$AGENT"
5040
+ load_agent_metadata
5041
+ [ "$AGENT_EXECUTION_LOCATION" != "remote" ] || agent_fail "remote bindings do not use lark-cli profiles"
3731
5042
  APP_ID="$(binding_json_field bot.app_id)"
3732
5043
  APP_SECRET="$(binding_json_field bot.app_secret)"
3733
5044
  BOT_NAME="$(binding_json_field bot.display_name)"
5045
+ APP_TENANT_BRAND="$(binding_json_field bot.tenant_brand)"
5046
+ APP_TENANT_BRAND="${APP_TENANT_BRAND:-feishu}"
3734
5047
  LARK_CLI_PROFILE="$(binding_json_field bot.lark_cli_profile)"
3735
5048
  [ -n "$APP_ID" ] && [ -n "$APP_SECRET" ] && [ -n "$LARK_CLI_PROFILE" ] || agent_fail "binding is missing Feishu credentials or profile"
3736
5049
  AAMP_TASK_INTERNAL_BINDING_JSON=""
3737
5050
  source_lark_env
3738
- ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE"
5051
+ ensure_lark_cli_profile "$APP_ID" "$APP_SECRET" "$LARK_CLI_PROFILE" "$APP_TENANT_BRAND"
3739
5052
  APP_SECRET=""
3740
5053
  emit_internal_result "{\"lark_cli_bin\":\"$(json_escape "$LARK_CLI_CMD")\",\"lark_cli_config_dir\":\"$(json_escape "${LARKSUITE_CLI_CONFIG_DIR:-}")\"}"
3741
5054
  }
3742
5055
 
3743
5056
  run_internal_action() {
3744
5057
  AAMP_TASK_INTERNAL="true"
3745
- ensure_node_toolchain
5058
+ if [ "$AAMP_TASK_ACTION" != "__probe-profile" ]; then
5059
+ ensure_node_toolchain
5060
+ fi
3746
5061
  case "$AAMP_TASK_ACTION" in
3747
5062
  __discover-agents)
3748
5063
  run_internal_discover_agents
@@ -3753,6 +5068,9 @@ run_internal_action() {
3753
5068
  __prepare-agent)
3754
5069
  run_internal_prepare_agent
3755
5070
  ;;
5071
+ __probe-profile)
5072
+ run_internal_probe_profile
5073
+ ;;
3756
5074
  __ensure-profile)
3757
5075
  run_internal_ensure_profile
3758
5076
  ;;
@@ -3766,6 +5084,92 @@ task_agent_controller_path() {
3766
5084
  printf '%s/bin/feishu-task-agent-controller.mjs\n' "$(task_agent_global_package_dir)"
3767
5085
  }
3768
5086
 
5087
+ task_agent_metadata_path() {
5088
+ printf '%s/bin/agent-metadata.mjs\n' "$(task_agent_global_package_dir)"
5089
+ }
5090
+
5091
+ local_package_override_is_supported() {
5092
+ local spec local_path
5093
+ spec="${1:-}"
5094
+ case "$spec" in
5095
+ file:*)
5096
+ local_path="${spec#file:}"
5097
+ [ -n "$local_path" ] && [ -d "$local_path" ]
5098
+ ;;
5099
+ *.tgz) [ -f "$spec" ] ;;
5100
+ *) return 1 ;;
5101
+ esac
5102
+ }
5103
+
5104
+ aime_local_package_override_path() {
5105
+ local directory filename spec
5106
+ spec="${1:-}"
5107
+ case "$spec" in
5108
+ -*|file:*|*://*) return 1 ;;
5109
+ esac
5110
+ if [[ "$spec" =~ (^|@)[[:alpha:]][[:alnum:]+.-]*: ]]; then
5111
+ return 1
5112
+ fi
5113
+ case "$spec" in
5114
+ *.tgz) [ -f "$spec" ] || return 1 ;;
5115
+ *) return 1 ;;
5116
+ esac
5117
+ case "$spec" in
5118
+ */*)
5119
+ directory="${spec%/*}"
5120
+ filename="${spec##*/}"
5121
+ [ -n "$directory" ] || directory="/"
5122
+ ;;
5123
+ *)
5124
+ directory="."
5125
+ filename="$spec"
5126
+ ;;
5127
+ esac
5128
+ directory="$(cd -P -- "$directory" 2>/dev/null && pwd -P)" || return 1
5129
+ printf '%s/%s\n' "$directory" "$filename"
5130
+ }
5131
+
5132
+ apply_task_agent_package_override_policy() {
5133
+ local aime_override_path
5134
+ ACP_BRIDGE_PKG="$AAMP_TASK_DEFAULT_ACP_BRIDGE_PKG"
5135
+ FEISHU_BRIDGE_PKG="$AAMP_TASK_DEFAULT_FEISHU_BRIDGE_PKG"
5136
+ AIME_ACP_PKG="$AAMP_TASK_DEFAULT_AIME_ACP_PKG"
5137
+ [ "$AAMP_TASK_ALLOW_PACKAGE_OVERRIDES" = "true" ] || return 0
5138
+
5139
+ if [ -n "$AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG" ]; then
5140
+ local_package_override_is_supported "$AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG" \
5141
+ || agent_fail "Local ACP Bridge package override is invalid."
5142
+ ACP_BRIDGE_PKG="$AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG"
5143
+ fi
5144
+ if [ -n "$AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG" ]; then
5145
+ local_package_override_is_supported "$AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG" \
5146
+ || agent_fail "Local Feishu Bridge package override is invalid."
5147
+ FEISHU_BRIDGE_PKG="$AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG"
5148
+ fi
5149
+ if [ -n "$AAMP_TASK_REQUESTED_AIME_ACP_PKG" ]; then
5150
+ aime_override_path="$(aime_local_package_override_path "$AAMP_TASK_REQUESTED_AIME_ACP_PKG")" \
5151
+ || agent_fail "AIME_ACP_PACKAGE_OVERRIDE_INVALID: AIME ACP package override must be an existing local .tgz file."
5152
+ AAMP_TASK_REQUESTED_AIME_ACP_PKG="$aime_override_path"
5153
+ AIME_ACP_PKG="$aime_override_path"
5154
+ fi
5155
+ }
5156
+
5157
+ load_agent_metadata() {
5158
+ local metadata_path metadata_json
5159
+ metadata_path="$(task_agent_metadata_path)"
5160
+ [ -r "$metadata_path" ] || agent_fail "Task Agent metadata is missing: $metadata_path"
5161
+ metadata_json="$(node "$metadata_path" --json "$AGENT")" \
5162
+ || agent_fail "failed to resolve Task Agent metadata for $AGENT"
5163
+ AGENT_EXECUTION_LOCATION="$(node -e 'const value=JSON.parse(process.argv[1]);process.stdout.write(value.executionLocation)' "$metadata_json")"
5164
+ AGENT_ATTACHMENT_POLICY="$(node -e 'const value=JSON.parse(process.argv[1]);process.stdout.write(value.attachmentPolicy || "")' "$metadata_json")"
5165
+ AGENT_TASK_DISPATCH_CONCURRENCY="$(node -e 'const value=JSON.parse(process.argv[1]);process.stdout.write(value.taskDispatchConcurrency ? String(value.taskDispatchConcurrency) : "")' "$metadata_json")"
5166
+ if [ -n "$AAMP_TASK_INTERNAL_EXECUTION_LOCATION" ] \
5167
+ && [ "$AAMP_TASK_INTERNAL_EXECUTION_LOCATION" != "$AGENT_EXECUTION_LOCATION" ]; then
5168
+ agent_fail "Task Agent execution metadata mismatch"
5169
+ fi
5170
+ AAMP_TASK_INTERNAL_EXECUTION_LOCATION="$AGENT_EXECUTION_LOCATION"
5171
+ }
5172
+
3769
5173
  run_task_agent_controller() {
3770
5174
  local controller bootstrap_path install_command
3771
5175
  controller="$(task_agent_controller_path)"
@@ -3778,6 +5182,17 @@ run_task_agent_controller() {
3778
5182
  export AAMP_TASK_NPX_BIN="$NPX_BIN"
3779
5183
  export AAMP_TASK_ACP_BRIDGE_PKG="$ACP_BRIDGE_PKG"
3780
5184
  export AAMP_TASK_FEISHU_BRIDGE_PKG="$FEISHU_BRIDGE_PKG"
5185
+ export AAMP_TASK_AIME_ACP_PKG="$AIME_ACP_PKG"
5186
+ export AAMP_TASK_ALLOW_PACKAGE_OVERRIDES
5187
+ if [ "$AAMP_TASK_ALLOW_PACKAGE_OVERRIDES" != "true" ]; then
5188
+ AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG=""
5189
+ AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG=""
5190
+ AAMP_TASK_REQUESTED_AIME_ACP_PKG=""
5191
+ fi
5192
+ export AAMP_TASK_PACKAGE_OVERRIDES_RESOLVED=true
5193
+ export AAMP_TASK_REQUESTED_ACP_BRIDGE_PKG
5194
+ export AAMP_TASK_REQUESTED_FEISHU_BRIDGE_PKG
5195
+ export AAMP_TASK_REQUESTED_AIME_ACP_PKG
3781
5196
  export AAMP_TASK_CODEX_ACP_PKG="$CODEX_ACP_PKG"
3782
5197
  export AAMP_TASK_AGENT_VERSION
3783
5198
  export AAMP_TASK_DEFAULT_AGENT="$AGENT"
@@ -3835,13 +5250,19 @@ main() {
3835
5250
  return 0
3836
5251
  fi
3837
5252
 
5253
+ apply_task_agent_package_override_policy
5254
+
3838
5255
  case "$AAMP_TASK_ACTION" in
3839
- __discover-agents|__register-binding|__prepare-agent|__ensure-profile)
5256
+ __discover-agents|__register-binding|__prepare-agent|__probe-profile|__ensure-profile)
3840
5257
  run_internal_action
3841
5258
  return 0
3842
5259
  ;;
3843
5260
  esac
3844
5261
 
5262
+ if [ "$AGENT" = "aime" ]; then
5263
+ ensure_agent_selection_available "$AGENT"
5264
+ fi
5265
+
3845
5266
  trap cleanup EXIT INT TERM HUP
3846
5267
  init_log_run
3847
5268
  validate_mock_fail_stage