magic-agent 1.0.0

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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +392 -0
  3. package/cmd/magic-agent/main.go +12 -0
  4. package/cmd/magic-client/main.go +144 -0
  5. package/go.mod +11 -0
  6. package/go.sum +12 -0
  7. package/internal/agent/arkclaw.go +587 -0
  8. package/internal/agent/arkclaw_stream.go +312 -0
  9. package/internal/agent/arkclaw_stream_test.go +428 -0
  10. package/internal/agent/arkclaw_test.go +827 -0
  11. package/internal/agent/ask.go +694 -0
  12. package/internal/agent/ask_test.go +587 -0
  13. package/internal/agent/attachments.go +200 -0
  14. package/internal/agent/attachments_test.go +481 -0
  15. package/internal/agent/capability.go +154 -0
  16. package/internal/agent/capability_test.go +190 -0
  17. package/internal/agent/claude.go +406 -0
  18. package/internal/agent/codebuddy.go +1044 -0
  19. package/internal/agent/codebuddy_account_test.go +128 -0
  20. package/internal/agent/codebuddy_gateway.go +479 -0
  21. package/internal/agent/codebuddy_gateway_stream.go +358 -0
  22. package/internal/agent/codebuddy_gateway_test.go +673 -0
  23. package/internal/agent/codex.go +599 -0
  24. package/internal/agent/codex_home.go +206 -0
  25. package/internal/agent/codex_home_test.go +130 -0
  26. package/internal/agent/codex_stream_test.go +97 -0
  27. package/internal/agent/codex_test.go +280 -0
  28. package/internal/agent/custom_models.go +197 -0
  29. package/internal/agent/custom_models_test.go +283 -0
  30. package/internal/agent/dsh.go +754 -0
  31. package/internal/agent/dsh_sdk.go +679 -0
  32. package/internal/agent/dsh_sdk_test.go +726 -0
  33. package/internal/agent/dsh_test.go +759 -0
  34. package/internal/agent/engine.go +469 -0
  35. package/internal/agent/engine_base.go +497 -0
  36. package/internal/agent/engine_base_test.go +308 -0
  37. package/internal/agent/engine_test.go +858 -0
  38. package/internal/agent/env.go +88 -0
  39. package/internal/agent/env_test.go +106 -0
  40. package/internal/agent/jsonextract.go +128 -0
  41. package/internal/agent/jsonextract_test.go +110 -0
  42. package/internal/agent/keepalive_test.go +216 -0
  43. package/internal/agent/llmengine.go +470 -0
  44. package/internal/agent/llmengine_test.go +551 -0
  45. package/internal/agent/llmhttp.go +437 -0
  46. package/internal/agent/llmhttp_test.go +304 -0
  47. package/internal/agent/llmmodels.go +205 -0
  48. package/internal/agent/login.go +27 -0
  49. package/internal/agent/main_test.go +23 -0
  50. package/internal/agent/models.go +686 -0
  51. package/internal/agent/models_test.go +877 -0
  52. package/internal/agent/openclaw.go +543 -0
  53. package/internal/agent/openclaw_acp.go +949 -0
  54. package/internal/agent/openclaw_acp_test.go +406 -0
  55. package/internal/agent/openclaw_test.go +451 -0
  56. package/internal/agent/output.go +191 -0
  57. package/internal/agent/permission.go +362 -0
  58. package/internal/agent/permission_test.go +526 -0
  59. package/internal/agent/prompt.go +65 -0
  60. package/internal/agent/runcmd.go +259 -0
  61. package/internal/agent/runcmd_test.go +81 -0
  62. package/internal/agent/runcmd_unix.go +56 -0
  63. package/internal/agent/runcmd_windows.go +66 -0
  64. package/internal/agent/runner.go +180 -0
  65. package/internal/agent/runner_test.go +263 -0
  66. package/internal/agent/spawnhook_test.go +96 -0
  67. package/internal/agent/stream.go +882 -0
  68. package/internal/agent/stream_test.go +565 -0
  69. package/internal/agent/streamjson.go +185 -0
  70. package/internal/agent/tags.go +52 -0
  71. package/internal/agent/toolkind.go +96 -0
  72. package/internal/agent/toolkind_test.go +70 -0
  73. package/internal/agent/tools_suffix_test.go +261 -0
  74. package/internal/agent/trae.go +342 -0
  75. package/internal/agent/workspace_test.go +166 -0
  76. package/internal/boundary/boundary_test.go +165 -0
  77. package/internal/cli/ask.go +1113 -0
  78. package/internal/cli/ask_test.go +1088 -0
  79. package/internal/cli/attach_test.go +239 -0
  80. package/internal/cli/contract_test.go +157 -0
  81. package/internal/cli/control.go +149 -0
  82. package/internal/cli/control_test.go +192 -0
  83. package/internal/cli/engines_test.go +205 -0
  84. package/internal/cli/events.go +198 -0
  85. package/internal/cli/events_test.go +166 -0
  86. package/internal/cli/keepalive_test.go +391 -0
  87. package/internal/cli/login.go +78 -0
  88. package/internal/cli/permission_test.go +295 -0
  89. package/internal/cli/prompt_source_test.go +222 -0
  90. package/internal/cli/root.go +752 -0
  91. package/internal/cli/session.go +486 -0
  92. package/internal/cli/session_test.go +202 -0
  93. package/internal/cli/sessionlog.go +325 -0
  94. package/internal/cli/sessionlog_command_test.go +204 -0
  95. package/internal/cli/sessionlog_test.go +262 -0
  96. package/internal/cli/sessionlog_user_test.go +129 -0
  97. package/internal/cli/toolkind_wire_test.go +84 -0
  98. package/internal/cli/workspace_test.go +188 -0
  99. package/internal/client/cors_test.go +136 -0
  100. package/internal/client/lan.go +68 -0
  101. package/internal/client/lan_test.go +53 -0
  102. package/internal/client/pair.go +163 -0
  103. package/internal/client/pair_test.go +109 -0
  104. package/internal/client/profile.go +184 -0
  105. package/internal/client/service.go +888 -0
  106. package/internal/client/service_test.go +654 -0
  107. package/internal/client/session_events_test.go +186 -0
  108. package/internal/client/session_messages_test.go +150 -0
  109. package/internal/config/config.go +439 -0
  110. package/internal/config/config_test.go +448 -0
  111. package/internal/session/session.go +447 -0
  112. package/internal/session/session_append.go +241 -0
  113. package/internal/session/session_append_test.go +161 -0
  114. package/internal/session/session_kill_unix_test.go +90 -0
  115. package/internal/session/session_mark_test.go +95 -0
  116. package/internal/session/session_test.go +237 -0
  117. package/internal/session/sessionlog.go +128 -0
  118. package/internal/session/sessionlog_read_test.go +157 -0
  119. package/npm/dist/darwin-arm64/magic-agent +0 -0
  120. package/npm/dist/darwin-x64/magic-agent +0 -0
  121. package/npm/dist/linux-arm64/magic-agent +0 -0
  122. package/npm/dist/linux-x64/magic-agent +0 -0
  123. package/npm/dist/win32-x64/magic-agent.exe +0 -0
  124. package/npm/install.js +121 -0
  125. package/npm/lib/platform.js +78 -0
  126. package/package.json +36 -0
@@ -0,0 +1,184 @@
1
+ package client
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ )
10
+
11
+ /* Profile 是「一台机器上装了几个 magic-* 应用」之间**唯一的差异来源**。
12
+ *
13
+ * 设计约束(见 magic-client 方案设计):profile **只描述差异,不描述结构** ——
14
+ * 一旦里面开始出现布局或流程描述,它就退化成第二套代码。
15
+ * 因此这里只留四类字段:应用身份(app)、界面入口(ui)、启用能力(capabilities / engines)、
16
+ * 数据位置(storage),外加业务模块清单(modules)。
17
+ */
18
+
19
+ // Profile 一个应用的集成配置。
20
+ type Profile struct {
21
+ App struct {
22
+ Name string `json:"name"`
23
+ BundleID string `json:"bundleId,omitempty"`
24
+ Icon string `json:"icon,omitempty"`
25
+ } `json:"app"`
26
+ UI struct {
27
+ // Entry 界面入口(相对 UIRoot)。
28
+ Entry string `json:"entry"`
29
+ // Theme 主题令牌名(paper-ink 等),由基础版 UI 解释。
30
+ Theme string `json:"theme,omitempty"`
31
+ // DefaultView 启动视图。
32
+ DefaultView string `json:"defaultView,omitempty"`
33
+ // ThemeVars 主题令牌覆盖(键值对;键写 `--page-brand` 或 `page-brand` 都行)。
34
+ // 插件把它注入页面根元素的 style,模块因此不必改基础版 UI 的样式文件。
35
+ ThemeVars map[string]string `json:"themeVars,omitempty"`
36
+ // ModulesDir 项目自己的模块资源目录(绝对路径,或相对 profile 文件所在目录)。
37
+ // 插件把它挂到 `/modules/` 下 —— 于是业务模块的文件**留在项目仓库里**,
38
+ // 不必塞进插件的 UI 目录。LoadProfile 会把它解析成绝对路径。
39
+ ModulesDir string `json:"modulesDir,omitempty"`
40
+ } `json:"ui"`
41
+ // Backend 项目自己的本地后端:插件的 `/api/*` 原样转发到这里。
42
+ //
43
+ // 为什么是代理而不是插件内置存储:业务数据(需求、用例、产物、设置)的**真相在项目那边**,
44
+ // 插件再建一份存储就是第二个真相源。有了它,业务视图可以逐块迁到插件侧而**不必重写业务逻辑**
45
+ //(前端搬过去,后端照旧)。没有配置时 `/api/*` 回 501 并点名这个字段,不静默 404。
46
+ Backend BackendConfig `json:"backend"`
47
+ // Capabilities 启用的通用能力(chat / capture / browser / files / terminal)。
48
+ // 空 = 只启用基础版 UI 自带的 chat。
49
+ Capabilities []string `json:"capabilities,omitempty"`
50
+ // Engines 允许出现的引擎白名单;空 = 全部(以 core 的 --contract 为准)。
51
+ Engines []string `json:"engines,omitempty"`
52
+ Storage struct {
53
+ // Dir 数据目录(默认 ~/.magic-client/<app-id>)。
54
+ Dir string `json:"dir,omitempty"`
55
+ // DB 业务库文件名。
56
+ DB string `json:"db,omitempty"`
57
+ } `json:"storage"`
58
+ // Modules 业务模块清单:插件只负责按需加载与提供接口,不含任何领域假设。
59
+ Modules []Module `json:"modules,omitempty"`
60
+ }
61
+
62
+ // Module 一个业务模块的声明。
63
+ type Module struct {
64
+ ID string `json:"id"`
65
+ // Entry 模块前端入口(相对 UIRoot),由基础版 UI 按视图激活时机动态加载。
66
+ Entry string `json:"entry,omitempty"`
67
+ // DataTables 该模块自己的表;插件只提供存储接口,不解释它们的含义。
68
+ DataTables []string `json:"dataTables,omitempty"`
69
+ }
70
+
71
+ // BackendConfig 项目自己的本地后端(`/api/*` 的转发目标)。
72
+ type BackendConfig struct {
73
+ // URL 后端地址,如 http://127.0.0.1:8931。
74
+ URL string `json:"url,omitempty"`
75
+ // Token 转发时由**服务端注入**的凭据(不暴露给浏览器)。
76
+ Token string `json:"token,omitempty"`
77
+ // TokenHeader 注入用的头名;空 = Authorization: Bearer <token>。
78
+ TokenHeader string `json:"tokenHeader,omitempty"`
79
+ }
80
+
81
+ // DefaultProfile 返回最小可用 profile(只有基础版 UI,没有业务模块)。
82
+ func DefaultProfile() Profile {
83
+ var p Profile
84
+ p.App.Name = "magic-client"
85
+ p.UI.Entry = "index.html"
86
+ p.UI.DefaultView = "chat"
87
+ p.Capabilities = []string{"chat"}
88
+ return p
89
+ }
90
+
91
+ // LoadProfile 读 profile。path 为空时返回 DefaultProfile。
92
+ //
93
+ // 取值优先级(与 core 的 MAGIC_AGENT_BIN 同风格):显式 --profile > 用户目录覆盖文件 > 内置默认。
94
+ // 用户覆盖文件的命名是 profiles/<app-id>.json,app-id 由文件名决定。
95
+ func LoadProfile(path string) (Profile, error) {
96
+ p := DefaultProfile()
97
+ if strings.TrimSpace(path) == "" {
98
+ return p, nil
99
+ }
100
+ data, err := os.ReadFile(path)
101
+ if err != nil {
102
+ return p, fmt.Errorf("读 profile 失败: %w", err)
103
+ }
104
+ if err := json.Unmarshal(data, &p); err != nil {
105
+ return p, fmt.Errorf("解析 profile 失败(%s): %w", path, err)
106
+ }
107
+ if strings.TrimSpace(p.UI.Entry) == "" {
108
+ p.UI.Entry = "index.html"
109
+ }
110
+ if strings.TrimSpace(p.App.Name) == "" {
111
+ p.App.Name = strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
112
+ }
113
+ // modulesDir 允许写成相对 profile 的路径 —— 这样 profile 可以跟着项目仓库走,
114
+ // 换台机器/换个 checkout 位置都不用改。
115
+ if d := strings.TrimSpace(p.UI.ModulesDir); d != "" && !filepath.IsAbs(d) {
116
+ p.UI.ModulesDir = filepath.Join(filepath.Dir(absOr(path)), d)
117
+ }
118
+ return p, nil
119
+ }
120
+
121
+ // absOr 取绝对路径;失败时原样返回(调用方只用来拼相对路径)。
122
+ func absOr(p string) string {
123
+ if abs, err := filepath.Abs(p); err == nil {
124
+ return abs
125
+ }
126
+ return p
127
+ }
128
+
129
+ // AppID 应用标识:优先 bundleId,其次名字小写,用于数据目录与用户覆盖文件命名。
130
+ func (p Profile) AppID() string {
131
+ if s := strings.TrimSpace(p.App.BundleID); s != "" {
132
+ return s
133
+ }
134
+ name := strings.ToLower(strings.TrimSpace(p.App.Name))
135
+ name = strings.NewReplacer(" ", "-", "/", "-", "\\", "-").Replace(name)
136
+ if name == "" {
137
+ return "magic-client"
138
+ }
139
+ return name
140
+ }
141
+
142
+ // DataDir 数据目录(~/.magic-client/<app-id>;profile 里给了就用给的)。
143
+ func (p Profile) DataDir() string {
144
+ if s := strings.TrimSpace(p.Storage.Dir); s != "" {
145
+ return expandHome(s)
146
+ }
147
+ home, err := os.UserHomeDir()
148
+ if err != nil {
149
+ return filepath.Join(os.TempDir(), "magic-client", p.AppID())
150
+ }
151
+ return filepath.Join(home, ".magic-client", p.AppID())
152
+ }
153
+
154
+ // HasCapability 该 profile 是否启用了某个通用能力。
155
+ func (p Profile) HasCapability(name string) bool {
156
+ for _, c := range p.Capabilities {
157
+ if strings.EqualFold(strings.TrimSpace(c), name) {
158
+ return true
159
+ }
160
+ }
161
+ return false
162
+ }
163
+
164
+ // AllowsEngine 引擎白名单判定(空名单 = 全放行)。
165
+ func (p Profile) AllowsEngine(name string) bool {
166
+ if len(p.Engines) == 0 {
167
+ return true
168
+ }
169
+ for _, e := range p.Engines {
170
+ if strings.EqualFold(strings.TrimSpace(e), name) {
171
+ return true
172
+ }
173
+ }
174
+ return false
175
+ }
176
+
177
+ func expandHome(p string) string {
178
+ if strings.HasPrefix(p, "~/") {
179
+ if home, err := os.UserHomeDir(); err == nil {
180
+ return filepath.Join(home, p[2:])
181
+ }
182
+ }
183
+ return p
184
+ }