tagword-codeagent 1.1.6__py3-none-any.whl

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 (173) hide show
  1. codeagent/__init__.py +10 -0
  2. codeagent/__main__.py +13 -0
  3. codeagent/cli/__init__.py +13 -0
  4. codeagent/cli/entrypoint.py +161 -0
  5. codeagent/cli/main.py +979 -0
  6. codeagent/core/__init__.py +2 -0
  7. codeagent/core/attachments.py +342 -0
  8. codeagent/core/audio_models.py +50 -0
  9. codeagent/core/bootstrap.py +178 -0
  10. codeagent/core/default_presets.py +159 -0
  11. codeagent/core/env.py +106 -0
  12. codeagent/core/folder_picker.py +151 -0
  13. codeagent/core/image_understanding.py +90 -0
  14. codeagent/core/models.py +6 -0
  15. codeagent/core/paths.py +238 -0
  16. codeagent/core/pricing.py +107 -0
  17. codeagent/core/process_ports.py +258 -0
  18. codeagent/core/seed_bridge.py +21 -0
  19. codeagent/core/settings.py +98 -0
  20. codeagent/core/speech_synth.py +330 -0
  21. codeagent/core/team_config.py +247 -0
  22. codeagent/core/team_manager.py +245 -0
  23. codeagent/core/token_counter.py +41 -0
  24. codeagent/core/tts_voices.py +103 -0
  25. codeagent/core/usage_billing.py +72 -0
  26. codeagent/core/vision_models.py +93 -0
  27. codeagent/memory/diary.py +144 -0
  28. codeagent/persona_defaults/README.md +12 -0
  29. codeagent/persona_defaults/agent.md +352 -0
  30. codeagent/persona_defaults/identity.md +29 -0
  31. codeagent/persona_defaults/memory.md +4 -0
  32. codeagent/persona_defaults/skills.md +325 -0
  33. codeagent/persona_defaults/soul.md +38 -0
  34. codeagent/persona_defaults/tools.md +41 -0
  35. codeagent/persona_defaults/user.md +18 -0
  36. codeagent/runtime/__init__.py +11 -0
  37. codeagent/runtime/agent_context.py +6 -0
  38. codeagent/runtime/agent_runtime.py +21 -0
  39. codeagent/runtime/compact_prompt.py +9 -0
  40. codeagent/runtime/compact_state.py +40 -0
  41. codeagent/runtime/llm_worker.py +132 -0
  42. codeagent/runtime/orchestrator.py +161 -0
  43. codeagent/runtime/prompt_enrichment.py +324 -0
  44. codeagent/runtime/session_manager.py +30 -0
  45. codeagent/runtime/task_split.py +38 -0
  46. codeagent/runtime/turn_loop.py +13 -0
  47. codeagent/runtime/worker.py +48 -0
  48. codeagent/server/__init__.py +505 -0
  49. codeagent/server/app_factory.py +1292 -0
  50. codeagent/server/attachment_api.py +261 -0
  51. codeagent/server/hub.py +175 -0
  52. codeagent/server/icon.png +0 -0
  53. codeagent/server/self_healing.py +143 -0
  54. codeagent/server/team_engine.py +243 -0
  55. codeagent/server/tts_api.py +51 -0
  56. codeagent/server/webui_api_app.py +3180 -0
  57. codeagent/skills/__init__.py +2 -0
  58. codeagent/skills/select.py +144 -0
  59. codeagent/tools/__init__.py +6 -0
  60. codeagent/tools/agent_tools.py +10 -0
  61. codeagent/web/__init__.py +1 -0
  62. codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  63. codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  64. codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  65. codeagent/web/auth_impl.py +326 -0
  66. codeagent/web/static/00-marked.min.js +6 -0
  67. codeagent/web/static/00-mermaid.min.js +2314 -0
  68. codeagent/web/static/00-purify.min.js +3 -0
  69. codeagent/web/static/00-storage.js +83 -0
  70. codeagent/web/static/00-utils.js +51 -0
  71. codeagent/web/static/01-theme.css +96 -0
  72. codeagent/web/static/01a-scroll.js +86 -0
  73. codeagent/web/static/01b-think.js +62 -0
  74. codeagent/web/static/01c-session-identity.js +81 -0
  75. codeagent/web/static/01c-session-thinking.js +61 -0
  76. codeagent/web/static/01c-session-tree-menus.js +349 -0
  77. codeagent/web/static/01c-session-tree.js +531 -0
  78. codeagent/web/static/01c-session-utilities.js +83 -0
  79. codeagent/web/static/01c-vision-model.js +132 -0
  80. codeagent/web/static/01d-image-gen-model.js +84 -0
  81. codeagent/web/static/01e-audio-model.js +97 -0
  82. codeagent/web/static/01f-compose-ui.js +88 -0
  83. codeagent/web/static/01g-tts-settings.js +128 -0
  84. codeagent/web/static/01h-music-model.js +85 -0
  85. codeagent/web/static/01i-model-stack.js +291 -0
  86. codeagent/web/static/01j-video-gen-model.js +85 -0
  87. codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
  88. codeagent/web/static/01k-model-stack-state.js +209 -0
  89. codeagent/web/static/01q-inflight.js +195 -0
  90. codeagent/web/static/02-reset.css +22 -0
  91. codeagent/web/static/02-session-ui.js +201 -0
  92. codeagent/web/static/03-layout-p1.css +2 -0
  93. codeagent/web/static/03-layout-p2.css +2 -0
  94. codeagent/web/static/03a-links-stream.js +88 -0
  95. codeagent/web/static/03b-stream-text.js +119 -0
  96. codeagent/web/static/03c-render-bubble.js +520 -0
  97. codeagent/web/static/03d-agent-bubble.js +126 -0
  98. codeagent/web/static/03e-tool-trace.js +182 -0
  99. codeagent/web/static/03f-timeline.js +54 -0
  100. codeagent/web/static/03g-bubble-tts.js +292 -0
  101. codeagent/web/static/04-components-p1.css +1 -0
  102. codeagent/web/static/04-components-p2b.css +1 -0
  103. codeagent/web/static/04-components-p3.css +2 -0
  104. codeagent/web/static/04-components-p4.css +1 -0
  105. codeagent/web/static/04-components-p5.css +1 -0
  106. codeagent/web/static/04-components-p6.css +58 -0
  107. codeagent/web/static/04-components-p7.css +4 -0
  108. codeagent/web/static/04-components-p8.css +1 -0
  109. codeagent/web/static/04-components-p9.css +1 -0
  110. codeagent/web/static/04-ws-connect.js +351 -0
  111. codeagent/web/static/04-ws-helpers.js +116 -0
  112. codeagent/web/static/05-chat-p1.css +1058 -0
  113. codeagent/web/static/05-chat-p2.css +1 -0
  114. codeagent/web/static/05-chat-p3.css +295 -0
  115. codeagent/web/static/05a-session-history.js +176 -0
  116. codeagent/web/static/05b-session-list.js +114 -0
  117. codeagent/web/static/05c-dialog.js +123 -0
  118. codeagent/web/static/05d-project-create.js +172 -0
  119. codeagent/web/static/05e-project-remote.js +143 -0
  120. codeagent/web/static/06-chat.js +552 -0
  121. codeagent/web/static/06-utilities.css +181 -0
  122. codeagent/web/static/06b-attachments.js +304 -0
  123. codeagent/web/static/06c-camera.js +488 -0
  124. codeagent/web/static/07-activity-bar.css +127 -0
  125. codeagent/web/static/09-git-p1.css +1 -0
  126. codeagent/web/static/09-git-p2.css +1 -0
  127. codeagent/web/static/09a-init-bind.js +110 -0
  128. codeagent/web/static/09b-panel-toggle.js +80 -0
  129. codeagent/web/static/09c-api-refresh.js +100 -0
  130. codeagent/web/static/09d-status-log.js +80 -0
  131. codeagent/web/static/09e-commit.js +80 -0
  132. codeagent/web/static/09f-remote.js +93 -0
  133. codeagent/web/static/11a-cron-parse.js +117 -0
  134. codeagent/web/static/11b-cron-panel.js +100 -0
  135. codeagent/web/static/11c-cron-card.js +133 -0
  136. codeagent/web/static/11d-cron-form.js +105 -0
  137. codeagent/web/static/11e-env-config.js +168 -0
  138. codeagent/web/static/11f-env-llm-a-state.js +136 -0
  139. codeagent/web/static/11f-env-llm-b-form.js +409 -0
  140. codeagent/web/static/11f-env-llm-c-board.js +322 -0
  141. codeagent/web/static/11g-env-paths-git.js +973 -0
  142. codeagent/web/static/11h-env-mcp-a-state.js +161 -0
  143. codeagent/web/static/11h-env-mcp-b-form.js +229 -0
  144. codeagent/web/static/11h-env-mcp-c-board.js +565 -0
  145. codeagent/web/static/12-plugins.js +127 -0
  146. codeagent/web/static/13a-skills.js +156 -0
  147. codeagent/web/static/13a-todo-utils.js +140 -0
  148. codeagent/web/static/13b-todo-fetch.js +106 -0
  149. codeagent/web/static/13b-tools.js +127 -0
  150. codeagent/web/static/13c-todo-crud.js +92 -0
  151. codeagent/web/static/14-activity-bar.js +177 -0
  152. codeagent/web/static/15-plans.css +194 -0
  153. codeagent/web/static/15-plans.js +219 -0
  154. codeagent/web/static/16-mobile.css +612 -0
  155. codeagent/web/static/16-mobile.js +178 -0
  156. codeagent/web/static/17-preset-ux.css +287 -0
  157. codeagent/web/static/18-agent-mgr.css +616 -0
  158. codeagent/web/static/18-agent-mgr.js +204 -0
  159. codeagent/web/static/24-health-dashboard.js +68 -0
  160. codeagent/web/static/99-brand-polish.css +152 -0
  161. codeagent/web/static/body.html +824 -0
  162. codeagent/web/static/setup/setup_form.html +171 -0
  163. codeagent/web/static/setup/setup_head.html +100 -0
  164. codeagent/web/static/setup/setup_tail.html +264 -0
  165. codeagent/web/static/setup/setup_test.html +38 -0
  166. codeagent/web/web_login.html +265 -0
  167. codeagent/web/webui.html +35 -0
  168. tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
  169. tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
  170. tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
  171. tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
  172. tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
  173. tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Git 面板 — Web UI 源代码管理(滑出面板版)
3
+ * 依赖: 09-git.css
4
+ *
5
+ * 点击 topbar 的 Git 按钮 (btnToggleGit) 或 activity bar 的 Git 图标,
6
+ * 滑出面板,与 Plan/Todo 面板互斥。
7
+ * 暴露 window._gitRefresh() 供外部调用。
8
+ */
9
+
10
+ (function() {
11
+ 'use strict';
12
+
13
+ /* =============================================
14
+ * 状态
15
+ * ============================================= */
16
+ var _initialized = false;
17
+ var _projectId = '';
18
+ var _currentView = 'status';
19
+
20
+ /* =============================================
21
+ * DOM 快捷引用
22
+ * ============================================= */
23
+ var $ = function(id) { return document.getElementById(id); };
24
+
25
+ /* =============================================
26
+ * 初始化
27
+ * ============================================= */
28
+ function _init() {
29
+ if (_initialized) return;
30
+ _initialized = true;
31
+
32
+ // 1. topbar Git 按钮 — 切换面板
33
+ var topbarBtn = $('btnToggleGit');
34
+ if (topbarBtn) {
35
+ topbarBtn.addEventListener('click', toggleGitPanel);
36
+ }
37
+
38
+ // 2. activity bar git 图标 — 同样切换面板
39
+ document.addEventListener('click', function(e) {
40
+ var btn = e.target.closest('[data-mode="git"]');
41
+ if (btn) {
42
+ // 拦截 activity bar 的 git 点击,改为切换面板
43
+ e.preventDefault();
44
+ e.stopPropagation();
45
+ toggleGitPanel();
46
+ }
47
+ });
48
+
49
+ // 3. 面板内按钮
50
+ bindClick('gitPanelRefresh', _refreshAll);
51
+ bindClick('gitPanelCommit', _doCommit);
52
+ bindClick('gitPanelPush', function() { _doGit('push'); });
53
+ bindClick('gitPanelPull', function() { _doGit('pull'); });
54
+
55
+ // 4. Tab 切换
56
+ var tabs = document.querySelectorAll('.git-slide-panel__tab');
57
+ tabs.forEach(function(tab) {
58
+ tab.addEventListener('click', function() {
59
+ tabs.forEach(function(t) { t.classList.remove('is-active'); });
60
+ this.classList.add('is-active');
61
+ _currentView = this.getAttribute('data-view') || 'status';
62
+ _refreshContent(_currentView);
63
+ });
64
+ });
65
+
66
+ // 5. 快捷键: Ctrl+Enter 提交
67
+ document.addEventListener('keydown', function(e) {
68
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
69
+ var input = $('gitPanelCommitMsg');
70
+ if (input && document.activeElement === input) {
71
+ e.preventDefault();
72
+ _doCommit();
73
+ }
74
+ }
75
+ });
76
+
77
+ // 6. 监听项目切换
78
+ window.addEventListener('project-changed', function(e) {
79
+ _projectId = e.detail.projectId || '';
80
+ if (gitIsVisible()) _refreshAll();
81
+ });
82
+
83
+ // 7. 远程仓库 — 只读显示,跳转到配置页编辑
84
+ bindClick('gitRemoteTestBtn', function() { _testRemote(); });
85
+ // 配置页跳转链接
86
+ var configLinks = document.querySelectorAll('#gitRemoteConfigLink, #gitRemoteConfigLinkEmpty');
87
+ configLinks.forEach(function(link) {
88
+ link.addEventListener('click', function(e) {
89
+ e.preventDefault();
90
+ // 切换到配置页
91
+ if (typeof activatePage === 'function') activatePage('config');
92
+ // 关闭 Git 面板
93
+ var panel = $('gitPanel');
94
+ var btn = $('btnToggleGit');
95
+ if (panel) panel.style.display = 'none';
96
+ if (btn) btn.classList.remove('is-active');
97
+ });
98
+ });
99
+ if (typeof projectId !== 'undefined') _projectId = projectId;
100
+ }
101
+
102
+ /** 安全绑定点击 */
103
+ function bindClick(id, fn) {
104
+ var el = $(id);
105
+ if (el) el.addEventListener('click', fn);
106
+ }
107
+
108
+ /* =============================================
109
+ * 面板切换(与 Plan/Todo 互斥)
110
+ * ============================================= */
@@ -0,0 +1,80 @@
1
+ function gitIsVisible() {
2
+ var panel = $('gitPanel');
3
+ return panel && panel.style.display === 'flex';
4
+ }
5
+
6
+ function toggleGitPanel() {
7
+ var opening = !gitIsVisible();
8
+ var panel = $('gitPanel');
9
+ var btn = $('btnToggleGit');
10
+ if (!panel) return;
11
+
12
+ panel.style.display = opening ? 'flex' : 'none';
13
+ if (btn) btn.classList.toggle('is-active', opening);
14
+ trySetLS(STORAGE_KEYS.GIT_PANEL_OPEN, opening ? '1' : '0');
15
+
16
+ // 互斥:打开 Git 时关闭 Plan 和 Todo
17
+ if (opening) {
18
+ _closeOtherPanels();
19
+ _refreshAll();
20
+ }
21
+ }
22
+
23
+ function _closeOtherPanels() {
24
+ var planP = $('planPanel');
25
+ var planB = $('btnTogglePlans');
26
+ if (planP && planP.style.display !== 'none') {
27
+ planP.style.display = 'none';
28
+ if (planB) planB.classList.remove('is-active');
29
+ trySetLS(STORAGE_KEYS.PLAN_PANEL_OPEN, '0');
30
+ }
31
+ var todoP = $('todoPanel');
32
+ var todoB = $('btnToggleTodos');
33
+ if (todoP && todoP.style.display !== 'none') {
34
+ todoP.style.display = 'none';
35
+ if (todoB) todoB.classList.remove('is-active');
36
+ trySetLS(STORAGE_KEYS.TODO_PANEL_OPEN, '0');
37
+ }
38
+ }
39
+
40
+ // 外部监听:如果 Plan/Todo 打开了,自动关闭 Git
41
+ document.addEventListener('click', function(e) {
42
+ var planBtn = e.target.closest('#btnTogglePlans');
43
+ var todoBtn = e.target.closest('#btnToggleTodos');
44
+ if (planBtn || todoBtn) {
45
+ var panel = $('gitPanel');
46
+ var btn = $('btnToggleGit');
47
+ if (panel && panel.style.display === 'flex') {
48
+ panel.style.display = 'none';
49
+ if (btn) btn.classList.remove('is-active');
50
+ trySetLS(STORAGE_KEYS.GIT_PANEL_OPEN, '0');
51
+ }
52
+ }
53
+ }, true);
54
+
55
+ // 恢复上次打开状态
56
+ try {
57
+ if (tryGetLS(STORAGE_KEYS.GIT_PANEL_OPEN) === '1') {
58
+ var planP = $('planPanel');
59
+ var todoP = $('todoPanel');
60
+ if ((planP && planP.style.display === 'flex') || (todoP && todoP.style.display === 'flex')) {
61
+ // 对面已开,不覆盖
62
+ } else {
63
+ var panel = $('gitPanel');
64
+ var btn = $('btnToggleGit');
65
+ if (panel) panel.style.display = 'flex';
66
+ if (btn) btn.classList.add('is-active');
67
+ setTimeout(_refreshAll, 150);
68
+ }
69
+ }
70
+ } catch (_) {}
71
+
72
+ /* =============================================
73
+ * Git API
74
+ * ============================================= */
75
+ function _gitApi(command, args, message) {
76
+ _projectId = (typeof projectId !== 'undefined') ? projectId : _projectId;
77
+ var body = { command: command, args: args || '', message: message || '' };
78
+ if (_projectId) body.project_id = _projectId;
79
+ return fetch('/api/ui/git', {
80
+ method: 'POST',
@@ -0,0 +1,100 @@
1
+ headers: { 'Content-Type': 'application/json' },
2
+ body: JSON.stringify(body),
3
+ }).then(function(r) { return r.json(); });
4
+ }
5
+
6
+ /* =============================================
7
+ * 刷新
8
+ * ============================================= */
9
+ function _refreshAll() {
10
+ // 检查当前项目是否有关联路径
11
+ var pid = _projectId || (typeof projectId !== 'undefined' ? projectId : '');
12
+ var projPath = _getProjectPath(pid);
13
+ if (pid && !projPath) {
14
+ var content = $('gitPanelContent');
15
+ if (content) {
16
+ content.innerHTML = '<div class="git-panel__notice" id="gitPanelNoPath">' +
17
+ '<div style="padding:24px;text-align:center;color:var(--text-muted,#888);">' +
18
+ '<div style="font-size:32px;margin-bottom:10px;">📂</div>' +
19
+ '<div style="font-weight:600;margin-bottom:6px;color:var(--text,#1a1a2e);">未设置项目目录</div>' +
20
+ '<div style="font-size:13px;margin-bottom:16px;line-height:1.5;">设置工作目录后,Git 和文件树才能操作项目文件</div>' +
21
+ '<button class="btn btn--primary btn--sm" id="btnSetProjectPath">设置目录</button>' +
22
+ '</div></div>';
23
+ // 绑定设置目录按钮
24
+ var setBtn = document.getElementById('btnSetProjectPath');
25
+ if (setBtn) {
26
+ setBtn.addEventListener('click', function() {
27
+ // 优先用已有项目目录对话框(与侧栏 ⋮ → 工作目录 统一),fallback 到 prompt
28
+ if (typeof showProjectDirectoryDialog === 'function') {
29
+ showProjectDirectoryDialog(pid);
30
+ } else {
31
+ var p = prompt(
32
+ '请输入工作目录路径:\n(支持 ~ 和 $HOME 等环境变量扩展,也支持 Windows %USERPROFILE%)',
33
+ _getProjectPath(pid) || ''
34
+ );
35
+ if (!p || !p.trim()) return;
36
+ fetch('/api/ui/projects/path', {
37
+ method: 'POST',
38
+ headers: {'Content-Type': 'application/json'},
39
+ credentials: 'same-origin',
40
+ body: JSON.stringify({agent_id: agentId, project_id: pid, path: p.trim()})
41
+ }).then(function(r) { return r.json(); }).then(function(resp) {
42
+ if (resp.detail) { alert('设置失败:' + resp.detail); return; }
43
+ var resolvedPath = resp.project ? resp.project.path : p.trim();
44
+ if (treeProjectsCache && treeProjectsCache.aid === agentId) {
45
+ treeProjectsCache.projects.forEach(function(x) {
46
+ if (x.id === pid) x.path = resolvedPath;
47
+ });
48
+ }
49
+ window.dispatchEvent(new CustomEvent('project-changed', {detail: {projectId: pid}}));
50
+ _refreshAll();
51
+ }).catch(function() { alert('设置失败'); });
52
+ }
53
+ });
54
+ }
55
+ }
56
+ return;
57
+ }
58
+ _refreshBranch();
59
+ _refreshContent(_currentView);
60
+ _refreshRemote();
61
+ }
62
+
63
+ function _getProjectPath(pid) {
64
+ if (!pid || typeof treeProjectsCache === 'undefined' || !treeProjectsCache) return '';
65
+ if (treeProjectsCache.aid !== agentId) return '';
66
+ for (var i = 0; i < treeProjectsCache.projects.length; i++) {
67
+ if (treeProjectsCache.projects[i].id === pid && treeProjectsCache.projects[i].path) {
68
+ return treeProjectsCache.projects[i].path;
69
+ }
70
+ }
71
+ return '';
72
+ }
73
+
74
+ function _refreshBranch() {
75
+ _gitApi('branch').then(function(r) {
76
+ var el = $('gitPanelBranch');
77
+ if (!el) return;
78
+ if (r.result) {
79
+ var match = r.result.match(/\S+$/);
80
+ var name = match ? match[0] : 'main';
81
+ el.textContent = '🌿 ' + name;
82
+ } else {
83
+ el.textContent = '';
84
+ }
85
+ });
86
+ }
87
+
88
+ function _refreshContent(view) {
89
+ var content = $('gitPanelContent');
90
+ if (!content) return;
91
+ if (view === 'status') _loadStatus(content);
92
+ else if (view === 'log') _loadLog(content);
93
+ else if (view === 'diff') _loadDiff(content);
94
+ }
95
+
96
+ /* =============================================
97
+ * 状态视图
98
+ * ============================================= */
99
+ function _loadStatus(container) {
100
+ container.innerHTML = '<div class="git-panel__loading">加载状态中...</div>';
@@ -0,0 +1,80 @@
1
+ _gitApi('status').then(function(r) {
2
+ if (r.error) {
3
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(r.error) + '</div>';
4
+ return;
5
+ }
6
+ var output = r.result || '';
7
+ _renderStatus(container, output);
8
+ }).catch(function(err) {
9
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(err.message) + '</div>';
10
+ });
11
+ }
12
+
13
+ function _renderStatus(container, output) {
14
+ if (!output || output.trim() === '' || output.trim() === '(无输出)') {
15
+ container.innerHTML = '<div style="color:var(--success,#98c379);padding:12px;text-align:center;">✅ 工作区干净,无改动</div>';
16
+ return;
17
+ }
18
+
19
+ var lines = output.split('\n');
20
+ var sections = [];
21
+ var currentSection = null;
22
+
23
+ lines.forEach(function(line) {
24
+ var trimmed = line.trim();
25
+ if (/^(📌|已暂存|Changes to be committed)/i.test(trimmed)) {
26
+ currentSection = { label: '已暂存', icon: '📌', files: [] };
27
+ sections.push(currentSection); return;
28
+ }
29
+ if (/^(📝|未暂存|Changes not staged)/i.test(trimmed)) {
30
+ currentSection = { label: '未暂存', icon: '📝', files: [] };
31
+ sections.push(currentSection); return;
32
+ }
33
+ if (/^(🆕|未跟踪|Untracked)/i.test(trimmed)) {
34
+ currentSection = { label: '未跟踪', icon: '🆕', files: [] };
35
+ sections.push(currentSection); return;
36
+ }
37
+ if (/^(✅|工作区干净)/i.test(trimmed)) {
38
+ container.innerHTML = '<div style="color:var(--success,#98c379);padding:12px;text-align:center;">✅ 工作区干净,无改动</div>';
39
+ return;
40
+ }
41
+ if (trimmed && trimmed.length >= 3 && currentSection) {
42
+ var statusChar = trimmed[0];
43
+ var filePath = trimmed.substring(1).trim();
44
+ if (filePath) currentSection.files.push({ status: statusChar, path: filePath });
45
+ }
46
+ });
47
+
48
+ if (sections.length === 0) {
49
+ container.innerHTML = '<div style="color:var(--success,#98c379);padding:12px;text-align:center;">✅ 工作区干净,无改动</div>';
50
+ return;
51
+ }
52
+
53
+ var html = '';
54
+ sections.forEach(function(sec) {
55
+ if (sec.files.length === 0) return;
56
+ html += '<div style="font-weight:500;font-size:11px;padding:6px 12px 2px;color:var(--text-muted,#888);">'
57
+ + sec.icon + ' ' + sec.label + ' <span style="font-weight:400;">(' + sec.files.length + ')</span></div>';
58
+ sec.files.forEach(function(f) {
59
+ var icon = _statusIcon(f.status);
60
+ var cls = _statusClass(f.status);
61
+ html += '<div class="git-panel__file" data-file="' + escAttr(f.path) + '">'
62
+ + '<span class="git-panel__file-icon ' + cls + '">' + icon + '</span>'
63
+ + '<span class="git-panel__file-name">' + escapeHtml(f.path) + '</span></div>';
64
+ });
65
+ });
66
+ container.innerHTML = html;
67
+
68
+ container.querySelectorAll('.git-panel__file').forEach(function(el) {
69
+ el.addEventListener('click', function() {
70
+ var path = this.getAttribute('data-file');
71
+ if (path) _showFileDiff(path);
72
+ });
73
+ });
74
+ }
75
+
76
+ /* =============================================
77
+ * 历史 / 差异 视图
78
+ * ============================================= */
79
+ function _loadLog(container) {
80
+ container.innerHTML = '<div class="git-panel__loading">加载提交历史中...</div>';
@@ -0,0 +1,80 @@
1
+ _gitApi('log', '--oneline --graph --decorate -30').then(function(r) {
2
+ if (r.result) {
3
+ container.innerHTML = '<pre class="git-diff-pre">' + escapeHtml(r.result) + '</pre>';
4
+ } else if (r.error) {
5
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(r.error) + '</div>';
6
+ } else {
7
+ container.innerHTML = '<div style="color:var(--text-muted,#888);padding:12px;text-align:center;">暂无提交记录</div>';
8
+ }
9
+ }).catch(function(err) {
10
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(err.message) + '</div>';
11
+ });
12
+ }
13
+
14
+ function _loadDiff(container) {
15
+ container.innerHTML = '<div class="git-panel__loading">加载差异中...</div>';
16
+ _gitApi('diff').then(function(r) {
17
+ if (r.result) {
18
+ container.innerHTML = '<pre class="git-diff-pre">' + escapeHtml(r.result) + '</pre>';
19
+ } else if (r.error) {
20
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(r.error) + '</div>';
21
+ } else {
22
+ container.innerHTML = '<div style="color:var(--text-muted,#888);padding:12px;text-align:center;">无差异内容</div>';
23
+ }
24
+ }).catch(function(err) {
25
+ container.innerHTML = '<div style="color:var(--danger,#e06c75);padding:8px 12px;">❌ ' + escapeHtml(err.message) + '</div>';
26
+ });
27
+ }
28
+
29
+ function _showFileDiff(filepath) {
30
+ _gitApi('diff', filepath).then(function(r) {
31
+ var content = $('gitPanelContent');
32
+ if (content && r.result) {
33
+ content.innerHTML = '<pre class="git-diff-pre">' + escapeHtml(r.result) + '</pre>';
34
+ _setActiveTab('diff');
35
+ }
36
+ });
37
+ }
38
+
39
+ function _setActiveTab(view) {
40
+ _currentView = view;
41
+ document.querySelectorAll('.git-slide-panel__tab').forEach(function(t) {
42
+ t.classList.toggle('is-active', t.getAttribute('data-view') === view);
43
+ });
44
+ }
45
+
46
+ /* =============================================
47
+ * 提交
48
+ * ============================================= */
49
+ function _doCommit() {
50
+ var msgEl = $('gitPanelCommitMsg');
51
+ if (!msgEl) return;
52
+ var msg = msgEl.value.trim();
53
+ if (!msg) {
54
+ msgEl.focus();
55
+ msgEl.style.borderColor = 'var(--danger,#e06c75)';
56
+ setTimeout(function() { msgEl.style.borderColor = ''; }, 2000);
57
+ return;
58
+ }
59
+ var btn = $('gitPanelCommit');
60
+ if (btn) { btn.disabled = true; btn.textContent = '提交中...'; }
61
+ _gitApi('commit', '', msg).then(function(r) {
62
+ if (r.result) { msgEl.value = ''; _showNotification(r.result); _refreshAll(); }
63
+ else if (r.error) _showNotification('❌ ' + r.error);
64
+ if (btn) { btn.disabled = false; btn.textContent = '提交'; }
65
+ }).catch(function(err) {
66
+ _showNotification('❌ ' + err.message);
67
+ if (btn) { btn.disabled = false; btn.textContent = '提交'; }
68
+ });
69
+ }
70
+
71
+ function _doGit(command) {
72
+ var btnId = command === 'push' ? 'gitPanelPush' : 'gitPanelPull';
73
+ var btn = $(btnId);
74
+ if (btn) { btn.disabled = true; btn.textContent = command === 'push' ? '推送中...' : '拉取中...'; }
75
+ _gitApi(command).then(function(r) {
76
+ _showNotification(r.result || r.error || '完成');
77
+ _refreshAll();
78
+ if (btn) { btn.disabled = false; btn.textContent = command === 'push' ? '↑ 推送' : '↓ 拉取'; }
79
+ }).catch(function(err) {
80
+ _showNotification('❌ ' + err.message);
@@ -0,0 +1,93 @@
1
+ if (btn) { btn.disabled = false; btn.textContent = command === 'push' ? '↑ 推送' : '↓ 拉取'; }
2
+ });
3
+ }
4
+
5
+ /* =============================================
6
+ * 通知 / 工具
7
+ * ============================================= */
8
+ function _showNotification(msg) {
9
+ var hint = $('gitPanelHint');
10
+ if (!hint) return;
11
+ hint.textContent = msg.substring(0, 200);
12
+ hint.style.color = msg.indexOf('❌') >= 0 ? 'var(--danger,#e06c75)' : 'var(--success,#98c379)';
13
+ if (window._gitNotifTimer) clearTimeout(window._gitNotifTimer);
14
+ window._gitNotifTimer = setTimeout(function() { hint.textContent = ''; hint.style.color = ''; }, 5000);
15
+ }
16
+
17
+ /* =============================================
18
+ * 远程仓库(只读显示)
19
+ * ============================================= */
20
+
21
+ function _refreshRemote() {
22
+ _gitApi('remote').then(function(r) {
23
+ var infoBar = $('gitRemoteInfoBar');
24
+ var emptyBar = $('gitRemoteEmptyBar');
25
+ var display = $('gitRemoteDisplay');
26
+ if (!infoBar || !emptyBar || !display) return;
27
+
28
+ if (r.result && r.result.indexOf('未配置') < 0) {
29
+ var lines = r.result.split('\n');
30
+ var remotes = [];
31
+ lines.forEach(function(l) {
32
+ var m = l.match(/(\S+)\s+(\S+)\s+\((fetch|push)\)/);
33
+ if (m && m[3] === 'fetch') remotes.push({ name: m[1], url: m[2] });
34
+ });
35
+ if (remotes.length > 0) {
36
+ var html = '';
37
+ remotes.forEach(function(r) {
38
+ html += '<div style="padding:1px 0;font-size:11px;" title="' + escAttr(r.url) + '">';
39
+ html += '🔗 <strong>' + escapeHtml(r.name) + '</strong> → ' + escapeHtml(_shortUrl(r.url));
40
+ html += '</div>';
41
+ });
42
+ display.innerHTML = html;
43
+ infoBar.style.display = 'flex';
44
+ emptyBar.style.display = 'none';
45
+ return;
46
+ }
47
+ }
48
+ infoBar.style.display = 'none';
49
+ emptyBar.style.display = 'flex';
50
+ });
51
+ }
52
+
53
+ function _shortUrl(url) {
54
+ if (!url) return '';
55
+ // 简短显示 URL: github.com/user/repo
56
+ var m = url.replace(/^git@/, '').replace(/^https?:\/\//, '').replace(/\.git$/, '');
57
+ return m.length > 45 ? m.substring(0, 42) + '...' : m;
58
+ }
59
+
60
+ function _testRemote(remoteName) {
61
+ var name = remoteName || 'origin';
62
+ _showNotification('🔌 测试连接中...');
63
+ _gitApi('test-remote', name).then(function(r) {
64
+ _showNotification(r.result || r.error || '测试完成');
65
+ }).catch(function(err) {
66
+ _showNotification('❌ ' + err.message);
67
+ });
68
+ }
69
+
70
+ function _statusIcon(c) {
71
+ var icons = { 'M': '📝', 'A': '➕', 'D': '🗑️', 'R': '🔀', '?': '❓', '!': '⚠️', 'C': '📋' };
72
+ return icons[c] || '📄';
73
+ }
74
+ function _statusClass(c) {
75
+ var classes = { 'M': 'git-status--M', 'A': 'git-status--A', 'D': 'git-status--D', 'R': 'git-status--R', '?': 'git-status--U' };
76
+ return classes[c] || '';
77
+ }
78
+ // escapeHtml/escAttr 已统一在 00-utils.js(顶层声明)
79
+
80
+ /* =============================================
81
+ * 对外暴露
82
+ * ============================================= */
83
+ window._gitRefresh = _refreshAll;
84
+
85
+ /* =============================================
86
+ * 启动
87
+ * ============================================= */
88
+ if (document.readyState === 'loading') {
89
+ document.addEventListener('DOMContentLoaded', _init);
90
+ } else {
91
+ _init();
92
+ }
93
+ })();
@@ -0,0 +1,117 @@
1
+ /* ================================================================
2
+ * 11-config.js
3
+ * - Config page: codeagent.env raw editor
4
+ * - Chat session env config (structured UI controls)
5
+ * - Cron (codeagent.cron.json) card-based UI (like LLM Presets)
6
+ * - LLM Presets management (multi-model)
7
+ *
8
+ * Depends on (provided by earlier files):
9
+ * agentId, refreshChatModelSelect
10
+ * ================================================================ */
11
+
12
+ // ---------------- Cron card-based UI (natural language, no raw CRON) ----------------
13
+
14
+ /** Frequency modes: how the user picks schedule in natural language */
15
+ const CRON_FREQ_MODES = [
16
+ { id: 'minutes', label: '每 N 分钟' },
17
+ { id: 'hours', label: '每 N 小时' },
18
+ { id: 'daily', label: '每天固定时间' },
19
+ { id: 'weekly', label: '每周固定时间' },
20
+ { id: 'monthly', label: '每月固定日期' },
21
+ ];
22
+
23
+ const DAY_NAMES = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
24
+
25
+ /** Parse a cron expression into a friendly frequency descriptor */
26
+ function parseCronToFreq(expr) {
27
+ if (!expr) return { mode: 'daily', minute: '0', hour: '8', interval: 30, dayOfWeek: '1', dayOfMonth: '1' };
28
+ var parts = (expr || '').trim().split(/\s+/);
29
+ while (parts.length < 5) parts.push('*');
30
+ var min = parts[0], hour = parts[1], dom = parts[2], mon = parts[3], dow = parts[4];
31
+ var result = { minute: min, hour: hour, interval: 30, dayOfWeek: dow, dayOfMonth: dom };
32
+
33
+ if (hour === '*' && dom === '*' && mon === '*' && dow === '*' && min.indexOf('/') !== -1) {
34
+ result.mode = 'minutes';
35
+ result.interval = parseInt(min.split('/')[1]) || 30;
36
+ } else if (min !== '*' && dom === '*' && mon === '*' && dow === '*' && hour.indexOf('/') !== -1) {
37
+ result.mode = 'hours';
38
+ result.interval = parseInt(hour.split('/')[1]) || 1;
39
+ result.minute = min;
40
+ } else if (hour !== '*' && dom === '*' && mon === '*' && dow === '*') {
41
+ result.mode = 'daily';
42
+ result.minute = min === '*' ? '0' : min;
43
+ result.hour = hour === '*' ? '8' : hour;
44
+ } else if (hour !== '*' && mon === '*' && dow !== '*' && dom === '*') {
45
+ result.mode = 'weekly';
46
+ result.minute = min === '*' ? '0' : min;
47
+ result.hour = hour === '*' ? '8' : hour;
48
+ result.dayOfWeek = dow;
49
+ } else if (hour !== '*' && dom !== '*' && mon === '*' && dow === '*') {
50
+ result.mode = 'monthly';
51
+ result.minute = min === '*' ? '0' : min;
52
+ result.hour = hour === '*' ? '8' : hour;
53
+ result.dayOfMonth = dom;
54
+ } else {
55
+ result.mode = 'daily';
56
+ result.minute = min === '*' ? '0' : min;
57
+ result.hour = hour === '*' ? '8' : hour;
58
+ }
59
+ return result;
60
+ }
61
+
62
+ /** Convert frequency descriptor back to a standard 5-field cron */
63
+ function freqToCron(f) {
64
+ switch (f.mode) {
65
+ case 'minutes':
66
+ var iv = parseInt(f.interval) || 30;
67
+ return '*/' + iv + ' * * * *';
68
+ case 'hours':
69
+ var ih = parseInt(f.interval) || 1;
70
+ return (f.minute || '0') + ' */' + ih + ' * * *';
71
+ case 'daily':
72
+ return (f.minute || '0') + ' ' + (f.hour || '8') + ' * * *';
73
+ case 'weekly':
74
+ return (f.minute || '0') + ' ' + (f.hour || '8') + ' * * ' + (f.dayOfWeek || '1');
75
+ case 'monthly':
76
+ return (f.minute || '0') + ' ' + (f.hour || '8') + ' ' + (f.dayOfMonth || '1') + ' * *';
77
+ default:
78
+ return '0 8 * * *';
79
+ }
80
+ }
81
+
82
+ /** Describe a cron expression in full Chinese */
83
+ function describeCron(expr) {
84
+ if (!expr) return '未设置';
85
+ var f = parseCronToFreq(expr);
86
+ var timeStr = (f.hour || '8') + ':' + ((f.minute || '0').length < 2 ? '0' : '') + (f.minute || '0');
87
+ switch (f.mode) {
88
+ case 'minutes': return '每 ' + (parseInt(f.interval) || 30) + ' 分钟执行一次';
89
+ case 'hours': return '每 ' + (parseInt(f.interval) || 1) + ' 小时(' + f.minute + ' 分)执行一次';
90
+ case 'daily': return '每天 ' + timeStr + ' 执行';
91
+ case 'weekly': return '每' + (DAY_NAMES[parseInt(f.dayOfWeek)] || '周' + f.dayOfWeek) + ' ' + timeStr + ' 执行';
92
+ case 'monthly': return '每月 ' + f.dayOfMonth + ' 日 ' + timeStr + ' 执行';
93
+ default: return '每天 ' + timeStr + ' 执行';
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Stable ASCII id for codeagent.cron.json / APScheduler.
99
+ * Pure CJK names become cron-<timestamp>-<rand>; mixed names keep Latin slug.
100
+ * Display text should use ``title`` (see save payload).
101
+ */
102
+ function cronSafeJobId(displayName) {
103
+ var s = String(displayName || '').trim();
104
+ var slug = s.replace(/[^a-zA-Z0-9_-]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '').toLowerCase();
105
+ if (!slug || /^_+$/.test(slug)) {
106
+ return 'cron-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7);
107
+ }
108
+ if (slug.charAt(0) === '_') {
109
+ return 'job-' + slug.replace(/^_+/, '');
110
+ }
111
+ return slug;
112
+ }
113
+
114
+ // ---- Global state ----
115
+ let cronGlobalEnabled = false;
116
+
117
+ async function loadCronPanel() {