work-ally 0.2.0-alpha.1
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.
- package/AGENTS.md +110 -0
- package/DASHBOARD.md +160 -0
- package/PRODUCT.md +113 -0
- package/README.md +403 -0
- package/ally.sh +171 -0
- package/bridge/src/approval-rules.ts +360 -0
- package/bridge/src/channel-delivery.ts +207 -0
- package/bridge/src/channel-types.ts +22 -0
- package/bridge/src/channels/fake/adapter.ts +31 -0
- package/bridge/src/channels/feishu/adapter.ts +411 -0
- package/bridge/src/channels/feishu/approvals.ts +6 -0
- package/bridge/src/channels/feishu/formatter.ts +276 -0
- package/bridge/src/channels/feishu/normalize.ts +368 -0
- package/bridge/src/codex-config.ts +52 -0
- package/bridge/src/config.ts +240 -0
- package/bridge/src/fake-runtime-client.ts +505 -0
- package/bridge/src/handoff-service.ts +494 -0
- package/bridge/src/logger.ts +194 -0
- package/bridge/src/memory-digest.ts +186 -0
- package/bridge/src/receiver-approval-autonomy.ts +158 -0
- package/bridge/src/receiver-control-core.ts +140 -0
- package/bridge/src/receiver-control-work-session.ts +218 -0
- package/bridge/src/receiver-control.ts +83 -0
- package/bridge/src/receiver-delivery.ts +136 -0
- package/bridge/src/receiver-helpers.ts +96 -0
- package/bridge/src/receiver-human-gate.ts +333 -0
- package/bridge/src/receiver-inbound-preflight.ts +162 -0
- package/bridge/src/receiver-recovery.ts +236 -0
- package/bridge/src/receiver-runtime-callbacks.ts +367 -0
- package/bridge/src/receiver-runtime-policy.ts +132 -0
- package/bridge/src/receiver-runtime-state.ts +124 -0
- package/bridge/src/receiver-support-actions.ts +189 -0
- package/bridge/src/receiver-thread-start.ts +57 -0
- package/bridge/src/receiver-turn-coordination.ts +94 -0
- package/bridge/src/receiver-turn-execution.ts +257 -0
- package/bridge/src/receiver-turn-failure.ts +143 -0
- package/bridge/src/receiver-turn-result.ts +185 -0
- package/bridge/src/receiver-turn-steer.ts +70 -0
- package/bridge/src/receiver-work-session.ts +76 -0
- package/bridge/src/receiver.ts +329 -0
- package/bridge/src/router.ts +62 -0
- package/bridge/src/runtime-client-agent-messages.ts +150 -0
- package/bridge/src/runtime-client-message-dispatch.ts +176 -0
- package/bridge/src/runtime-client-protocol.ts +411 -0
- package/bridge/src/runtime-client-request-ops.ts +56 -0
- package/bridge/src/runtime-client-run-turn.ts +158 -0
- package/bridge/src/runtime-client-thread-ops.ts +270 -0
- package/bridge/src/runtime-client-transport.ts +309 -0
- package/bridge/src/runtime-client-turn-poll.ts +224 -0
- package/bridge/src/runtime-client-turn-read.ts +185 -0
- package/bridge/src/runtime-client-turn-state.ts +105 -0
- package/bridge/src/runtime-client.ts +344 -0
- package/bridge/src/runtime-user-input.ts +403 -0
- package/bridge/src/scheduler.ts +239 -0
- package/bridge/src/server-handoff-command.ts +364 -0
- package/bridge/src/server-main.ts +80 -0
- package/bridge/src/server-routine-command.ts +60 -0
- package/bridge/src/server-routine-execution.ts +222 -0
- package/bridge/src/server-runtime-app-support.ts +107 -0
- package/bridge/src/server-runtime-app.ts +238 -0
- package/bridge/src/server-thread-sync-command.ts +63 -0
- package/bridge/src/server.ts +17 -0
- package/bridge/src/session-store-delivery.ts +220 -0
- package/bridge/src/session-store-human-gate.ts +380 -0
- package/bridge/src/session-store-inbound-acceptance.ts +66 -0
- package/bridge/src/session-store-meta.ts +134 -0
- package/bridge/src/session-store-turn-ledger.ts +272 -0
- package/bridge/src/session-store.ts +380 -0
- package/bridge/src/system-notify.ts +220 -0
- package/bridge/src/thread-sync.ts +200 -0
- package/bridge/src/translator.ts +494 -0
- package/bridge/src/types.ts +289 -0
- package/bridge/src/utils.ts +104 -0
- package/bridge/src/work-session-store.ts +471 -0
- package/docs/.gitkeep +0 -0
- package/docs/architecture/codex-feishu-bridge-proposal.md +2742 -0
- package/docs/completed/FEATURE-feishu-markdown-and-reply-support.md +327 -0
- package/docs/completed/README.md +21 -0
- package/docs/completed/SPEC-approval-autonomy-and-safe-defaults.md +205 -0
- package/docs/completed/SPEC-approval-batch-and-strict-reply-shortcuts.md +153 -0
- package/docs/completed/SPEC-conversation-noise-reduction-and-busy-input-gate.md +538 -0
- package/docs/completed/SPEC-engineering-sop-skillization.md +190 -0
- package/docs/completed/SPEC-faithful-bridge-core-thinning-v2.md +376 -0
- package/docs/completed/SPEC-faithful-bridge-core-thinning.md +1071 -0
- package/docs/completed/SPEC-group-chat-sender-identity.md +301 -0
- package/docs/completed/SPEC-middleware-exception-visibility.md +227 -0
- package/docs/completed/SPEC-nightly-memory-digest-visibility.md +121 -0
- package/docs/completed/SPEC-project-group-chat-human-centered-conversation-mapping.md +326 -0
- package/docs/completed/SPEC-remove-cli-persona-bootstrap.md +201 -0
- package/docs/developer-workflow.md +49 -0
- package/docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md +239 -0
- package/docs/implementation/test-coverage-map.md +363 -0
- package/docs/implementation/work-ally-implementation-guide.md +790 -0
- package/docs/issues/README.md +10 -0
- package/docs/issues/pending/ANALYSIS-ally-premature-recovery-notice-and-task-state-semantics-2026-03-18.md +295 -0
- package/docs/issues/resolved/ANALYSIS-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +466 -0
- package/docs/issues/resolved/ANALYSIS-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +261 -0
- package/docs/issues/resolved/ANALYSIS-codex-app-server-transport-disconnect-semantics-2026-03-14.md +606 -0
- package/docs/issues/resolved/ANALYSIS-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +348 -0
- package/docs/issues/resolved/ANALYSIS-runtime-turn-delivery-and-recovery-2026-03-14.md +603 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +166 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +186 -0
- package/docs/issues/resolved/ANALYSIS-self-test-gap-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +166 -0
- package/docs/issues/resolved/REPORT-ally-runtime-turn-delivery-3b42fb8-2026-03-15.md +373 -0
- package/docs/manual-acceptance.md +127 -0
- package/docs/ops-runbook.md +44 -0
- package/docs/planning/FEATURE-memory-system.md +748 -0
- package/docs/planning/SPEC-active-turn-steer-and-context-compaction-visibility.md +269 -0
- package/docs/planning/SPEC-approval-rules-inheritance-and-local-validation-lane.md +450 -0
- package/docs/planning/SPEC-assistant-persona-bootstrap.md +199 -0
- package/docs/planning/SPEC-assistant-rename.md +610 -0
- package/docs/planning/SPEC-bridge-app-server-protocol-alignment.md +667 -0
- package/docs/planning/SPEC-claude-runtime-host-for-work-ally.md +434 -0
- package/docs/planning/SPEC-cli-feishu-codex-session-unification.md +236 -0
- package/docs/planning/SPEC-codex-same-machine-session-handoff.md +873 -0
- package/docs/planning/SPEC-feishu-reaction-shortcuts.md +282 -0
- package/docs/planning/SPEC-local-stable-release-boundary.md +166 -0
- package/docs/planning/SPEC-managed-thread-entry-and-surface-mobility.md +862 -0
- package/docs/planning/SPEC-minimal-bridge-semantics-and-user-visible-surface.md +362 -0
- package/docs/planning/SPEC-npm-alpha-distribution-and-install-first-release.md +222 -0
- package/docs/planning/SPEC-remove-websocket-runtime-transport.md +364 -0
- package/docs/planning/SPEC-runtime-abstraction-phase-1.md +424 -0
- package/docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md +274 -0
- package/docs/planning/SPEC-session-presence-and-state-visibility.md +397 -0
- package/docs/planning/SPEC-skill-first-capability-packaging.md +338 -0
- package/docs/planning/SPEC-stable-archive-contract.md +456 -0
- package/docs/planning/SPEC-supervised-start-boundary.md +127 -0
- package/docs/planning/SPEC-user-barrier-reduction-and-activation.md +832 -0
- package/docs/planning/ally-next.md +1278 -0
- package/docs/planning/assistant-workbench-spec.md +725 -0
- package/docs/planning/product-workbench.md +283 -0
- package/docs/product-onboarding.md +227 -0
- package/docs/product-spec-standard.md +528 -0
- package/docs/troubleshooting.md +45 -0
- package/docs/user-quickstart.md +46 -0
- package/internal/dispatch.sh +95 -0
- package/internal/lib/common.sh +1450 -0
- package/internal/modules/assistant/manage.sh +1312 -0
- package/internal/modules/bootstrap/setup.sh +144 -0
- package/internal/modules/config/init-env.sh +10 -0
- package/internal/modules/global/manage.sh +154 -0
- package/internal/modules/handoff/manage.sh +54 -0
- package/internal/modules/mcp/manage.sh +83 -0
- package/internal/modules/ops/logs.sh +76 -0
- package/internal/modules/routines/manage.sh +55 -0
- package/internal/modules/runtime/assistant-autosave.sh +26 -0
- package/internal/modules/runtime/restart.sh +6 -0
- package/internal/modules/runtime/start.sh +283 -0
- package/internal/modules/runtime/status.sh +194 -0
- package/internal/modules/runtime/stop.sh +55 -0
- package/internal/modules/runtime/supervisor.sh +216 -0
- package/internal/modules/runtime/update.sh +26 -0
- package/package.json +41 -0
- package/runtime/config/.gitkeep +0 -0
- package/runtime/host/.gitkeep +0 -0
- package/runtime/host/healthcheck-codex-app-server.ts +22 -0
- package/runtime/host/ping-pong-codex-app-server.ts +66 -0
- package/runtime/host/probe-codex-app-server.ts +115 -0
- package/skills/archive-reader/SKILL.md +9 -0
- package/skills/feishu-production-debug/SKILL.md +37 -0
- package/skills/feishu-production-debug/references/feishu-debug-order.md +49 -0
- package/skills/feishu-production-debug/references/platform-permission-baseline.md +23 -0
- package/skills/issue-to-spec-triage/SKILL.md +44 -0
- package/skills/issue-to-spec-triage/references/triage-rules.md +66 -0
- package/skills/memory-digest/SKILL.md +9 -0
- package/skills/post-implementation-closure/SKILL.md +39 -0
- package/skills/post-implementation-closure/references/closure-checklist.md +45 -0
- package/skills/post-implementation-closure/references/doc-drift-map.md +49 -0
- package/skills/product-spec/SKILL.md +244 -0
- package/templates/env.example +5 -0
- package/templates/routines/nightly-memory-digest.yaml +10 -0
- package/templates/workspace/AGENTS.md +26 -0
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
5
|
+
# shellcheck disable=SC1091
|
|
6
|
+
. "$SCRIPT_DIR/../../lib/common.sh"
|
|
7
|
+
work_ally_init_context
|
|
8
|
+
work_ally_ensure_state_dirs
|
|
9
|
+
|
|
10
|
+
SUBCMD="${1:-list}"
|
|
11
|
+
shift || true
|
|
12
|
+
|
|
13
|
+
cmd_name=$(work_ally_cmd_name)
|
|
14
|
+
|
|
15
|
+
usage() {
|
|
16
|
+
cat <<USAGE
|
|
17
|
+
Usage: $cmd_name assistant <subcmd> [args]
|
|
18
|
+
|
|
19
|
+
Subcommands:
|
|
20
|
+
add <name> --workspace <path> [--description TEXT] [--git-remote URL]
|
|
21
|
+
ensure <name> --workspace <path> [--description TEXT] [--git-remote URL]
|
|
22
|
+
bind <name> --workspace <path>
|
|
23
|
+
remove <name>
|
|
24
|
+
rename <old> <new>
|
|
25
|
+
list
|
|
26
|
+
show <name>
|
|
27
|
+
USAGE
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
assistant_profile_agents_template() {
|
|
31
|
+
local assistant_home="$1"
|
|
32
|
+
local workspace_root="$2"
|
|
33
|
+
cat <<'AGENTS' | sed \
|
|
34
|
+
-e "s|__WORKSPACE_ROOT__|$workspace_root|g" \
|
|
35
|
+
-e "s|__ASSISTANT_HOME__|$assistant_home|g"
|
|
36
|
+
# AGENTS.md
|
|
37
|
+
|
|
38
|
+
## 角色定位
|
|
39
|
+
|
|
40
|
+
你是由 `work-ally` 启动的命名 assistant。
|
|
41
|
+
|
|
42
|
+
你的 assistant 办公桌是你的长期住处。当前项目目录只是你此刻工作的现场。
|
|
43
|
+
|
|
44
|
+
## 两个主规则入口
|
|
45
|
+
|
|
46
|
+
- 当前项目规则入口:__WORKSPACE_ROOT__/AGENTS.md
|
|
47
|
+
- assistant desk 规则入口:__ASSISTANT_HOME__/AGENTS.md
|
|
48
|
+
|
|
49
|
+
如果项目 `AGENTS.md` 对角色、表达风格、协作方式有明确要求,应以项目要求为准。
|
|
50
|
+
|
|
51
|
+
## 关键路径
|
|
52
|
+
|
|
53
|
+
- assistant desk 根目录:__ASSISTANT_HOME__
|
|
54
|
+
- 当前项目目录:__WORKSPACE_ROOT__
|
|
55
|
+
- Runtime Codex home:__ASSISTANT_HOME__/.system/codex-home
|
|
56
|
+
|
|
57
|
+
不要把 desk 内的路径误解为相对于当前项目根目录的路径。它们都属于上面的 desk 路径。
|
|
58
|
+
|
|
59
|
+
## 读取模型
|
|
60
|
+
|
|
61
|
+
在 Codex 吃到两个 `AGENTS.md` 之后,你还必须继续读取这些 desk 资产:
|
|
62
|
+
|
|
63
|
+
1. __ASSISTANT_HOME__/SOUL.md
|
|
64
|
+
2. __ASSISTANT_HOME__/NOW.md
|
|
65
|
+
3. __ASSISTANT_HOME__/MISTAKES.md
|
|
66
|
+
4. __ASSISTANT_HOME__/MEMORY.md
|
|
67
|
+
|
|
68
|
+
其中:
|
|
69
|
+
|
|
70
|
+
- `SOUL.md`:你的名字、使命、人格、表达风格、做事方式
|
|
71
|
+
- `NOW.md`:当前态便签,工作时优先看,也允许你随手更新
|
|
72
|
+
- `MISTAKES.md`:高价值错题本,遇到高风险场景前先留意
|
|
73
|
+
- `MEMORY.md`:长期稳定记忆,不写日常碎片
|
|
74
|
+
|
|
75
|
+
## 边界
|
|
76
|
+
|
|
77
|
+
- 你的身份、记忆、对话视图、运行状态都在这个 desk 里。
|
|
78
|
+
- 项目的构建、测试、仓库规则来自当前项目目录。
|
|
79
|
+
- 不要把这个 desk 当成项目仓库。
|
|
80
|
+
- 稳定的项目事实应沉淀回项目自身,而不是长期堆在 desk 里。
|
|
81
|
+
|
|
82
|
+
## Desk 资产
|
|
83
|
+
|
|
84
|
+
- __ASSISTANT_HOME__/SOUL.md
|
|
85
|
+
- __ASSISTANT_HOME__/NOW.md
|
|
86
|
+
- __ASSISTANT_HOME__/MISTAKES.md
|
|
87
|
+
- __ASSISTANT_HOME__/MEMORY.md
|
|
88
|
+
- __ASSISTANT_HOME__/journal/
|
|
89
|
+
- __ASSISTANT_HOME__/conversations/
|
|
90
|
+
- __ASSISTANT_HOME__/.system/
|
|
91
|
+
|
|
92
|
+
补充说明:
|
|
93
|
+
|
|
94
|
+
- `conversations/` 是对话可读视图层,适合查看完整聊天链路。
|
|
95
|
+
- `.system/archive/` 是稳定原材料层,通常不需要主动阅读;只有在追溯事实、排查异常时才按需查阅。
|
|
96
|
+
- `SOUL.md` 允许用户自由编辑,但你默认不应静默改写它;如确需调整,应明确提出建议。
|
|
97
|
+
- nightly digest 默认只产出 `journal/` 与 `MEMORY.md`,不负责改写 `NOW.md`。
|
|
98
|
+
AGENTS
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
assistant_profile_soul_template() {
|
|
102
|
+
local name="$1"
|
|
103
|
+
|
|
104
|
+
cat <<'SOUL' | sed -e "s|__ASSISTANT_NAME__|$name|g"
|
|
105
|
+
# SOUL
|
|
106
|
+
|
|
107
|
+
## 你的名称
|
|
108
|
+
|
|
109
|
+
- 名字:__ASSISTANT_NAME__
|
|
110
|
+
|
|
111
|
+
## 用户补充设定
|
|
112
|
+
|
|
113
|
+
- 待使用者补充。
|
|
114
|
+
|
|
115
|
+
## 核心身份
|
|
116
|
+
|
|
117
|
+
- 你是长期驻留在 assistant desk 的命名助理。
|
|
118
|
+
- 你的职责是把问题想清楚、推进到位,并沉淀成可复用结果。
|
|
119
|
+
- 你进入项目是来工作,不是来改写项目规则;项目现场优先遵守项目 `AGENTS.md`。
|
|
120
|
+
|
|
121
|
+
## 使命
|
|
122
|
+
|
|
123
|
+
- 先判断问题本质,再推进到可执行结论。
|
|
124
|
+
- 帮使用者节省注意力:先说结果,再给关键依据,再给下一步。
|
|
125
|
+
- 把重要判断沉淀进记忆系统,而不是只留在一次对话里。
|
|
126
|
+
- 待使用者补充。
|
|
127
|
+
|
|
128
|
+
## 性格与表达风格
|
|
129
|
+
|
|
130
|
+
- 默认使用中文。
|
|
131
|
+
- 亲切、知性、直接、不盲从。
|
|
132
|
+
- 说话像可信赖的搭档,不像客服,也不写官话。
|
|
133
|
+
- 语气温和,但观点明确;该提醒时直接提醒。
|
|
134
|
+
- 主战场按即时消息场景处理:短、准、够用。
|
|
135
|
+
- 可以少量使用表情,但必须克制。
|
|
136
|
+
- 待使用者补充。
|
|
137
|
+
|
|
138
|
+
## IM 回复风格
|
|
139
|
+
|
|
140
|
+
- 先结论,后补充。
|
|
141
|
+
- 默认短回复;能一句话说清,就不要写一段。
|
|
142
|
+
- 只回答当前最需要的信息,不主动倾倒整包分析。
|
|
143
|
+
- 汇报优先短句和子弹点,方便几秒扫完。
|
|
144
|
+
- 非必要不展开推理过程;必须展开时再完整说明。
|
|
145
|
+
- 回复像给领导过目:亲切,但一针见血。
|
|
146
|
+
|
|
147
|
+
## 做事方式
|
|
148
|
+
|
|
149
|
+
- 先把事情做实,再汇报结果。
|
|
150
|
+
- 先抓主干,再补细节;先判断边界,再处理实现。
|
|
151
|
+
- 发现缺口时主动补齐;发现风险时及时提醒。
|
|
152
|
+
- 重要信息写入合适文件,不依赖短暂上下文。
|
|
153
|
+
- 用户已明确授权的计划内事项,直接推进,不重复确认。
|
|
154
|
+
- 待使用者补充。
|
|
155
|
+
|
|
156
|
+
## 禁忌与边界
|
|
157
|
+
|
|
158
|
+
- 不伪造事实,不装作知道。
|
|
159
|
+
- 不因为迎合而放弃独立判断。
|
|
160
|
+
- 不把项目规则覆盖成个人偏好。
|
|
161
|
+
- 不为显得完整而堆低价值噪音。
|
|
162
|
+
- 不写啰嗦的大段废话。
|
|
163
|
+
- 未经明确授权,不静默改写本文件。
|
|
164
|
+
- 待使用者补充。
|
|
165
|
+
SOUL
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
assistant_profile_global_config_path() {
|
|
169
|
+
printf '%s/.codex/config.toml\n' "$HOME"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
assistant_profile_default_config_template() {
|
|
173
|
+
cat <<'CONFIG'
|
|
174
|
+
model = "gpt-5"
|
|
175
|
+
reasoning_effort = "medium"
|
|
176
|
+
verbosity = "medium"
|
|
177
|
+
CONFIG
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
assistant_profile_config_template() {
|
|
181
|
+
local global_config
|
|
182
|
+
global_config=$(assistant_profile_global_config_path)
|
|
183
|
+
if [ -f "$global_config" ]; then
|
|
184
|
+
awk '
|
|
185
|
+
BEGIN {
|
|
186
|
+
skip_projects = 0
|
|
187
|
+
}
|
|
188
|
+
/^\[projects\.[^]]+\][[:space:]]*$/ {
|
|
189
|
+
skip_projects = 1
|
|
190
|
+
next
|
|
191
|
+
}
|
|
192
|
+
/^\[/ {
|
|
193
|
+
skip_projects = 0
|
|
194
|
+
}
|
|
195
|
+
skip_projects {
|
|
196
|
+
next
|
|
197
|
+
}
|
|
198
|
+
{
|
|
199
|
+
print
|
|
200
|
+
}
|
|
201
|
+
' "$global_config"
|
|
202
|
+
return 0
|
|
203
|
+
fi
|
|
204
|
+
assistant_profile_default_config_template
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
assistant_profile_ensure_trusted_project() {
|
|
208
|
+
local config_file="$1"
|
|
209
|
+
local project_root="$2"
|
|
210
|
+
[ -n "$project_root" ] || return 0
|
|
211
|
+
[ -f "$config_file" ] || return 0
|
|
212
|
+
if grep -Fq "[projects.\"$project_root\"]" "$config_file"; then
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
printf '\n[projects."%s"]\ntrust_level = "trusted"\n' "$project_root" >> "$config_file"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
assistant_profile_remove_trusted_project() {
|
|
219
|
+
local config_file="$1"
|
|
220
|
+
local project_root="$2"
|
|
221
|
+
local tmp_file
|
|
222
|
+
[ -f "$config_file" ] || return 0
|
|
223
|
+
[ -n "$project_root" ] || return 0
|
|
224
|
+
|
|
225
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-codex-config.XXXXXX")
|
|
226
|
+
awk -v project_root="$project_root" '
|
|
227
|
+
BEGIN {
|
|
228
|
+
skip = 0
|
|
229
|
+
section_header = "[projects.\"" project_root "\"]"
|
|
230
|
+
}
|
|
231
|
+
$0 == section_header {
|
|
232
|
+
skip = 1
|
|
233
|
+
next
|
|
234
|
+
}
|
|
235
|
+
skip && /^\[/ {
|
|
236
|
+
skip = 0
|
|
237
|
+
print
|
|
238
|
+
next
|
|
239
|
+
}
|
|
240
|
+
skip {
|
|
241
|
+
next
|
|
242
|
+
}
|
|
243
|
+
{
|
|
244
|
+
print
|
|
245
|
+
}
|
|
246
|
+
' "$config_file" > "$tmp_file"
|
|
247
|
+
mv "$tmp_file" "$config_file"
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
assistant_profile_set_top_level_setting() {
|
|
251
|
+
local config_file="$1"
|
|
252
|
+
local key="$2"
|
|
253
|
+
local value="$3"
|
|
254
|
+
local tmp_file
|
|
255
|
+
[ -f "$config_file" ] || return 0
|
|
256
|
+
|
|
257
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-codex-config.XXXXXX")
|
|
258
|
+
awk -v key="$key" -v value="$value" '
|
|
259
|
+
BEGIN {
|
|
260
|
+
in_section = 0
|
|
261
|
+
replaced = 0
|
|
262
|
+
}
|
|
263
|
+
/^\[/ {
|
|
264
|
+
if (!in_section && !replaced) {
|
|
265
|
+
print key " = " value
|
|
266
|
+
print ""
|
|
267
|
+
replaced = 1
|
|
268
|
+
}
|
|
269
|
+
in_section = 1
|
|
270
|
+
print
|
|
271
|
+
next
|
|
272
|
+
}
|
|
273
|
+
{
|
|
274
|
+
if (!in_section && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") {
|
|
275
|
+
if (!replaced) {
|
|
276
|
+
print key " = " value
|
|
277
|
+
replaced = 1
|
|
278
|
+
}
|
|
279
|
+
next
|
|
280
|
+
}
|
|
281
|
+
print
|
|
282
|
+
}
|
|
283
|
+
END {
|
|
284
|
+
if (!replaced) {
|
|
285
|
+
if (NR > 0) {
|
|
286
|
+
print ""
|
|
287
|
+
}
|
|
288
|
+
print key " = " value
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
' "$config_file" > "$tmp_file"
|
|
292
|
+
mv "$tmp_file" "$config_file"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
assistant_profile_set_workspace_write_root() {
|
|
296
|
+
local config_file="$1"
|
|
297
|
+
local assistant_home="$2"
|
|
298
|
+
local tmp_file
|
|
299
|
+
[ -f "$config_file" ] || return 0
|
|
300
|
+
[ -n "$assistant_home" ] || return 0
|
|
301
|
+
|
|
302
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-codex-config.XXXXXX")
|
|
303
|
+
awk -v assistant_home="$assistant_home" '
|
|
304
|
+
BEGIN {
|
|
305
|
+
in_section = 0
|
|
306
|
+
section_found = 0
|
|
307
|
+
writable_written = 0
|
|
308
|
+
line_to_insert = "writable_roots = [\"" assistant_home "\"]"
|
|
309
|
+
}
|
|
310
|
+
function flush_if_needed() {
|
|
311
|
+
if (in_section && !writable_written) {
|
|
312
|
+
print line_to_insert
|
|
313
|
+
writable_written = 1
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/^\[sandbox_workspace_write\][[:space:]]*$/ {
|
|
317
|
+
print
|
|
318
|
+
in_section = 1
|
|
319
|
+
section_found = 1
|
|
320
|
+
next
|
|
321
|
+
}
|
|
322
|
+
/^\[/ {
|
|
323
|
+
flush_if_needed()
|
|
324
|
+
in_section = 0
|
|
325
|
+
print
|
|
326
|
+
next
|
|
327
|
+
}
|
|
328
|
+
{
|
|
329
|
+
if (in_section && $0 ~ /^[[:space:]]*writable_roots[[:space:]]*=/) {
|
|
330
|
+
if (!writable_written) {
|
|
331
|
+
print line_to_insert
|
|
332
|
+
writable_written = 1
|
|
333
|
+
}
|
|
334
|
+
next
|
|
335
|
+
}
|
|
336
|
+
print
|
|
337
|
+
}
|
|
338
|
+
END {
|
|
339
|
+
flush_if_needed()
|
|
340
|
+
if (!section_found) {
|
|
341
|
+
print ""
|
|
342
|
+
print "[sandbox_workspace_write]"
|
|
343
|
+
print line_to_insert
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
' "$config_file" > "$tmp_file"
|
|
347
|
+
mv "$tmp_file" "$config_file"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
assistant_profile_ensure_runtime_defaults() {
|
|
351
|
+
local config_file="$1"
|
|
352
|
+
local assistant_home="$2"
|
|
353
|
+
assistant_profile_set_top_level_setting "$config_file" "sandbox_mode" '"workspace-write"'
|
|
354
|
+
assistant_profile_set_top_level_setting "$config_file" "approval_policy" '"on-request"'
|
|
355
|
+
assistant_profile_set_workspace_write_root "$config_file" "$assistant_home"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
assistant_gitignore_template() {
|
|
359
|
+
cat <<'IGNORE'
|
|
360
|
+
.DS_Store
|
|
361
|
+
.system/config.env
|
|
362
|
+
.system/logs/
|
|
363
|
+
.system/cache/
|
|
364
|
+
.system/runtime/
|
|
365
|
+
.system/runs/
|
|
366
|
+
.system/codex-home/.personality_migration
|
|
367
|
+
.system/codex-home/logs_*.sqlite
|
|
368
|
+
.system/codex-home/logs_*.sqlite-shm
|
|
369
|
+
.system/codex-home/logs_*.sqlite-wal
|
|
370
|
+
.system/codex-home/state_*.sqlite
|
|
371
|
+
.system/codex-home/state_*.sqlite-shm
|
|
372
|
+
.system/codex-home/state_*.sqlite-wal
|
|
373
|
+
.system/codex-home/sessions/
|
|
374
|
+
.system/codex-home/shell_snapshots/
|
|
375
|
+
.system/codex-home/skills/
|
|
376
|
+
.system/codex-home/tmp/
|
|
377
|
+
IGNORE
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
assistant_gitignore_ensure_patterns() {
|
|
381
|
+
local assistant_home="$1"
|
|
382
|
+
local gitignore_file="$assistant_home/.gitignore"
|
|
383
|
+
local line
|
|
384
|
+
|
|
385
|
+
[ -f "$gitignore_file" ] || assistant_gitignore_template > "$gitignore_file"
|
|
386
|
+
|
|
387
|
+
while IFS= read -r line; do
|
|
388
|
+
[ -n "$line" ] || continue
|
|
389
|
+
if ! grep -Fqx "$line" "$gitignore_file"; then
|
|
390
|
+
printf '%s
|
|
391
|
+
' "$line" >> "$gitignore_file"
|
|
392
|
+
fi
|
|
393
|
+
done <<'IGNORE'
|
|
394
|
+
.DS_Store
|
|
395
|
+
.system/config.env
|
|
396
|
+
.system/logs/
|
|
397
|
+
.system/cache/
|
|
398
|
+
.system/runtime/
|
|
399
|
+
.system/runs/
|
|
400
|
+
.system/codex-home/.personality_migration
|
|
401
|
+
.system/codex-home/logs_*.sqlite
|
|
402
|
+
.system/codex-home/logs_*.sqlite-shm
|
|
403
|
+
.system/codex-home/logs_*.sqlite-wal
|
|
404
|
+
.system/codex-home/state_*.sqlite
|
|
405
|
+
.system/codex-home/state_*.sqlite-shm
|
|
406
|
+
.system/codex-home/state_*.sqlite-wal
|
|
407
|
+
.system/codex-home/sessions/
|
|
408
|
+
.system/codex-home/shell_snapshots/
|
|
409
|
+
.system/codex-home/skills/
|
|
410
|
+
.system/codex-home/tmp/
|
|
411
|
+
IGNORE
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
build_registry_entry() {
|
|
415
|
+
local name="$1"
|
|
416
|
+
local assistant_home="$2"
|
|
417
|
+
local codex_home="$3"
|
|
418
|
+
local workspace_root="$4"
|
|
419
|
+
local description="$5"
|
|
420
|
+
local created_at_override="${6:-}"
|
|
421
|
+
local updated_at_override="${7:-}"
|
|
422
|
+
local timestamp created_at updated_at
|
|
423
|
+
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
424
|
+
created_at="${created_at_override:-$timestamp}"
|
|
425
|
+
updated_at="${updated_at_override:-$timestamp}"
|
|
426
|
+
description=$(printf '%s' "$description" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
427
|
+
cat <<ENTRY
|
|
428
|
+
$name:
|
|
429
|
+
assistant_home: $assistant_home
|
|
430
|
+
codex_home: $codex_home
|
|
431
|
+
workspace_root: $workspace_root
|
|
432
|
+
description: "$description"
|
|
433
|
+
created_at: $created_at
|
|
434
|
+
updated_at: $updated_at
|
|
435
|
+
ENTRY
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
bootstrap_assistant() {
|
|
439
|
+
local name="$1"
|
|
440
|
+
local workspace_root="$2"
|
|
441
|
+
local description="$3"
|
|
442
|
+
local git_remote="$4"
|
|
443
|
+
local existing_workspace="${5:-}"
|
|
444
|
+
local assistant_home system_dir codex_home conversations_dir sessions_dir archive_dir journal_dir routines_dir runtime_dir logs_dir runs_dir cache_dir
|
|
445
|
+
|
|
446
|
+
assistant_home=$(work_ally_assistant_home "$name")
|
|
447
|
+
system_dir=$(work_ally_assistant_system_dir "$name")
|
|
448
|
+
codex_home=$(work_ally_assistant_codex_home "$name")
|
|
449
|
+
conversations_dir=$(work_ally_assistant_conversations_dir "$name")
|
|
450
|
+
sessions_dir=$(work_ally_assistant_sessions_dir "$name")
|
|
451
|
+
archive_dir=$(work_ally_assistant_archive_dir "$name")
|
|
452
|
+
journal_dir=$(work_ally_assistant_journal_dir "$name")
|
|
453
|
+
routines_dir=$(work_ally_assistant_routines_dir "$name")
|
|
454
|
+
runtime_dir=$(work_ally_assistant_runtime_dir "$name")
|
|
455
|
+
logs_dir=$(work_ally_assistant_logs_dir "$name")
|
|
456
|
+
runs_dir=$(work_ally_assistant_runs_dir "$name")
|
|
457
|
+
cache_dir=$(work_ally_assistant_cache_dir "$name")
|
|
458
|
+
|
|
459
|
+
mkdir -p \
|
|
460
|
+
"$assistant_home" \
|
|
461
|
+
"$system_dir" \
|
|
462
|
+
"$codex_home" \
|
|
463
|
+
"$conversations_dir" \
|
|
464
|
+
"$sessions_dir" \
|
|
465
|
+
"$archive_dir" \
|
|
466
|
+
"$journal_dir" \
|
|
467
|
+
"$routines_dir" \
|
|
468
|
+
"$runtime_dir" \
|
|
469
|
+
"$logs_dir" \
|
|
470
|
+
"$runs_dir" \
|
|
471
|
+
"$cache_dir"
|
|
472
|
+
|
|
473
|
+
if [ ! -f "$assistant_home/AGENTS.md" ]; then
|
|
474
|
+
assistant_profile_agents_template "$assistant_home" "$workspace_root" > "$assistant_home/AGENTS.md"
|
|
475
|
+
fi
|
|
476
|
+
if [ ! -f "$codex_home/AGENTS.md" ]; then
|
|
477
|
+
assistant_profile_agents_template "$assistant_home" "$workspace_root" > "$codex_home/AGENTS.md"
|
|
478
|
+
fi
|
|
479
|
+
if [ ! -f "$assistant_home/SOUL.md" ]; then
|
|
480
|
+
assistant_profile_soul_template "$name" > "$assistant_home/SOUL.md"
|
|
481
|
+
fi
|
|
482
|
+
if [ ! -f "$codex_home/config.toml" ]; then
|
|
483
|
+
assistant_profile_config_template > "$codex_home/config.toml"
|
|
484
|
+
fi
|
|
485
|
+
assistant_profile_remove_trusted_project "$codex_home/config.toml" "$existing_workspace"
|
|
486
|
+
assistant_profile_ensure_trusted_project "$codex_home/config.toml" "$workspace_root"
|
|
487
|
+
assistant_profile_ensure_trusted_project "$codex_home/config.toml" "$assistant_home"
|
|
488
|
+
assistant_profile_ensure_runtime_defaults "$codex_home/config.toml" "$assistant_home"
|
|
489
|
+
assistant_gitignore_ensure_patterns "$assistant_home"
|
|
490
|
+
if [ ! -f "$assistant_home/NOW.md" ]; then
|
|
491
|
+
cat > "$assistant_home/NOW.md" <<'NOW'
|
|
492
|
+
# NOW
|
|
493
|
+
|
|
494
|
+
## 当前在做什么
|
|
495
|
+
- 待更新
|
|
496
|
+
|
|
497
|
+
## 当前阻塞
|
|
498
|
+
- 待更新
|
|
499
|
+
|
|
500
|
+
## 下一步
|
|
501
|
+
- 待更新
|
|
502
|
+
NOW
|
|
503
|
+
fi
|
|
504
|
+
if [ ! -f "$assistant_home/MEMORY.md" ]; then
|
|
505
|
+
cat > "$assistant_home/MEMORY.md" <<'MEM'
|
|
506
|
+
# MEMORY
|
|
507
|
+
|
|
508
|
+
暂无长期记忆。
|
|
509
|
+
MEM
|
|
510
|
+
fi
|
|
511
|
+
if [ ! -f "$assistant_home/MISTAKES.md" ]; then
|
|
512
|
+
cat > "$assistant_home/MISTAKES.md" <<'MISTAKES'
|
|
513
|
+
# MISTAKES
|
|
514
|
+
|
|
515
|
+
暂无错题记录。
|
|
516
|
+
MISTAKES
|
|
517
|
+
fi
|
|
518
|
+
if [ ! -f "$journal_dir/README.md" ]; then
|
|
519
|
+
cat > "$journal_dir/README.md" <<'JOURNAL'
|
|
520
|
+
# Journal
|
|
521
|
+
|
|
522
|
+
按天记录整理后的工作日记与沉淀。
|
|
523
|
+
JOURNAL
|
|
524
|
+
fi
|
|
525
|
+
if [ ! -f "$conversations_dir/README.md" ]; then
|
|
526
|
+
cat > "$conversations_dir/README.md" <<'CONVERSATIONS'
|
|
527
|
+
# Conversations
|
|
528
|
+
|
|
529
|
+
这里保存从原材料派生出的对话可读视图,便于人和 assistant 回看完整聊天链路。
|
|
530
|
+
|
|
531
|
+
- 这里是 view layer,不是 source of truth。
|
|
532
|
+
- source of truth 在 `.system/archive/`。
|
|
533
|
+
CONVERSATIONS
|
|
534
|
+
fi
|
|
535
|
+
if [ ! -f "$archive_dir/README.md" ]; then
|
|
536
|
+
cat > "$archive_dir/README.md" <<'ARCHIVE'
|
|
537
|
+
# Archive
|
|
538
|
+
|
|
539
|
+
稳定原材料层,按 append-only 方式记录完整事实流水。
|
|
540
|
+
|
|
541
|
+
- 这里不是长期记忆。
|
|
542
|
+
- 这里不是默认阅读界面。
|
|
543
|
+
- 只有在追溯事实、审计与排障时才按需查阅。
|
|
544
|
+
ARCHIVE
|
|
545
|
+
fi
|
|
546
|
+
if [ ! -f "$routines_dir/nightly-memory-digest.yaml" ]; then
|
|
547
|
+
cp "$WORK_ALLY_IMPLEMENTATION_DIR/templates/routines/nightly-memory-digest.yaml" "$routines_dir/nightly-memory-digest.yaml"
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
work_ally_assistant_git_setup "$assistant_home" "$git_remote" || true
|
|
551
|
+
work_ally_assistant_git_checkpoint "$assistant_home" "bootstrap assistant $name" || true
|
|
552
|
+
|
|
553
|
+
printf '%s\n%s\n' "$assistant_home" "$codex_home"
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
ensure_registry_entry() {
|
|
557
|
+
local name="$1"
|
|
558
|
+
local assistant_home="$2"
|
|
559
|
+
local codex_home="$3"
|
|
560
|
+
local workspace_root="$4"
|
|
561
|
+
local description="$5"
|
|
562
|
+
|
|
563
|
+
if [ ! -f "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" ]; then
|
|
564
|
+
printf 'assistants:\n' > "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
if ! work_ally_registry_has_assistant "$name"; then
|
|
568
|
+
build_registry_entry "$name" "$assistant_home" "$codex_home" "$workspace_root" "$description" >> "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
569
|
+
fi
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
assistant_registry_update_binding() {
|
|
573
|
+
local name="$1"
|
|
574
|
+
local workspace_root="$2"
|
|
575
|
+
local tmp timestamp
|
|
576
|
+
|
|
577
|
+
[ -f "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" ] || work_ally_die "Assistant registry not found: $WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
578
|
+
|
|
579
|
+
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
580
|
+
tmp=$(mktemp "${TMPDIR:-/tmp}/work-ally-assistant-registry.XXXXXX")
|
|
581
|
+
|
|
582
|
+
awk -v assistant="$name" -v workspace="$workspace_root" -v updated_at="$timestamp" '
|
|
583
|
+
BEGIN {
|
|
584
|
+
in_block = 0
|
|
585
|
+
workspace_done = 0
|
|
586
|
+
updated_done = 0
|
|
587
|
+
}
|
|
588
|
+
function flush_missing() {
|
|
589
|
+
if (!in_block) {
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
if (!workspace_done) {
|
|
593
|
+
print " workspace_root: " workspace
|
|
594
|
+
}
|
|
595
|
+
if (!updated_done) {
|
|
596
|
+
print " updated_at: " updated_at
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
$0 ~ "^ " assistant ":$" {
|
|
600
|
+
in_block = 1
|
|
601
|
+
workspace_done = 0
|
|
602
|
+
updated_done = 0
|
|
603
|
+
print
|
|
604
|
+
next
|
|
605
|
+
}
|
|
606
|
+
in_block && /^ [^[:space:]]+:$/ {
|
|
607
|
+
flush_missing()
|
|
608
|
+
in_block = 0
|
|
609
|
+
print
|
|
610
|
+
next
|
|
611
|
+
}
|
|
612
|
+
in_block && /^ workspace_root: / {
|
|
613
|
+
print " workspace_root: " workspace
|
|
614
|
+
workspace_done = 1
|
|
615
|
+
next
|
|
616
|
+
}
|
|
617
|
+
in_block && /^ updated_at: / {
|
|
618
|
+
print " updated_at: " updated_at
|
|
619
|
+
updated_done = 1
|
|
620
|
+
next
|
|
621
|
+
}
|
|
622
|
+
{
|
|
623
|
+
print
|
|
624
|
+
}
|
|
625
|
+
END {
|
|
626
|
+
flush_missing()
|
|
627
|
+
}
|
|
628
|
+
' "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" > "$tmp"
|
|
629
|
+
|
|
630
|
+
mv "$tmp" "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
assistant_profile_rewrite_workspace_reference() {
|
|
634
|
+
local target_file="$1"
|
|
635
|
+
local old_workspace="$2"
|
|
636
|
+
local new_workspace="$3"
|
|
637
|
+
|
|
638
|
+
[ -f "$target_file" ] || return 0
|
|
639
|
+
[ -n "$old_workspace" ] || return 0
|
|
640
|
+
[ "$old_workspace" = "$new_workspace" ] && return 0
|
|
641
|
+
|
|
642
|
+
work_ally_require_cmd python3
|
|
643
|
+
OLD_WORKSPACE="$old_workspace" NEW_WORKSPACE="$new_workspace" \
|
|
644
|
+
python3 - "$target_file" <<'PY'
|
|
645
|
+
from pathlib import Path
|
|
646
|
+
import os
|
|
647
|
+
import sys
|
|
648
|
+
path = Path(sys.argv[1])
|
|
649
|
+
old = os.environ['OLD_WORKSPACE']
|
|
650
|
+
new = os.environ['NEW_WORKSPACE']
|
|
651
|
+
text = path.read_text()
|
|
652
|
+
path.write_text(text.replace(old, new))
|
|
653
|
+
PY
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
assistant_profile_rewrite_soul_name() {
|
|
657
|
+
local target_file="$1"
|
|
658
|
+
local old_name="$2"
|
|
659
|
+
local new_name="$3"
|
|
660
|
+
|
|
661
|
+
[ -f "$target_file" ] || return 0
|
|
662
|
+
[ -n "$old_name" ] || return 0
|
|
663
|
+
[ -n "$new_name" ] || return 0
|
|
664
|
+
[ "$old_name" = "$new_name" ] && return 0
|
|
665
|
+
|
|
666
|
+
work_ally_require_cmd python3
|
|
667
|
+
OLD_ASSISTANT_NAME="$old_name" NEW_ASSISTANT_NAME="$new_name" \
|
|
668
|
+
python3 - "$target_file" <<'PY'
|
|
669
|
+
from pathlib import Path
|
|
670
|
+
import os
|
|
671
|
+
import re
|
|
672
|
+
import sys
|
|
673
|
+
path = Path(sys.argv[1])
|
|
674
|
+
text = path.read_text()
|
|
675
|
+
pattern = rf'^(\s*-\s*名字:){re.escape(os.environ["OLD_ASSISTANT_NAME"])}$'
|
|
676
|
+
text, count = re.subn(pattern, rf'\1{os.environ["NEW_ASSISTANT_NAME"]}', text, count=1, flags=re.MULTILINE)
|
|
677
|
+
path.write_text(text)
|
|
678
|
+
PY
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
assistant_profile_rewrite_managed_env() {
|
|
682
|
+
local target_file="$1"
|
|
683
|
+
local new_name="$2"
|
|
684
|
+
local new_home="$3"
|
|
685
|
+
local new_codex_home="$4"
|
|
686
|
+
local new_state_dir="$5"
|
|
687
|
+
local tmp_file
|
|
688
|
+
|
|
689
|
+
[ -f "$target_file" ] || return 0
|
|
690
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-managed-env.XXXXXX")
|
|
691
|
+
|
|
692
|
+
awk -v name="$new_name" -v home="$new_home" -v codex="$new_codex_home" -v state_dir="$new_state_dir" '
|
|
693
|
+
BEGIN {
|
|
694
|
+
wrote_name = 0
|
|
695
|
+
}
|
|
696
|
+
/^WORK_ALLY_ASSISTANT_NAME=/ {
|
|
697
|
+
print "WORK_ALLY_ASSISTANT_NAME=" name
|
|
698
|
+
wrote_name = 1
|
|
699
|
+
next
|
|
700
|
+
}
|
|
701
|
+
/^WORK_ALLY_ASSISTANT_HOME=/ {
|
|
702
|
+
print "WORK_ALLY_ASSISTANT_HOME=" home
|
|
703
|
+
next
|
|
704
|
+
}
|
|
705
|
+
/^WORK_ALLY_ASSISTANT_CODEX_HOME=/ {
|
|
706
|
+
print "WORK_ALLY_ASSISTANT_CODEX_HOME=" codex
|
|
707
|
+
next
|
|
708
|
+
}
|
|
709
|
+
/^WORK_ALLY_STATE_DIR=/ {
|
|
710
|
+
print "WORK_ALLY_STATE_DIR=" state_dir
|
|
711
|
+
next
|
|
712
|
+
}
|
|
713
|
+
{
|
|
714
|
+
print
|
|
715
|
+
}
|
|
716
|
+
END {
|
|
717
|
+
if (!wrote_name) {
|
|
718
|
+
print "WORK_ALLY_ASSISTANT_NAME=" name
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
' "$target_file" > "$tmp_file"
|
|
722
|
+
|
|
723
|
+
mv "$tmp_file" "$target_file"
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
assistant_work_sessions_root() {
|
|
727
|
+
local assistant_home="$1"
|
|
728
|
+
printf '%s/.system/runtime/work-sessions\n' "$assistant_home"
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
assistant_work_session_blocking_summary() {
|
|
732
|
+
local root="$1"
|
|
733
|
+
local assistant_name="$2"
|
|
734
|
+
|
|
735
|
+
[ -d "$root" ] || return 0
|
|
736
|
+
work_ally_require_cmd python3
|
|
737
|
+
python3 - "$root" "$assistant_name" <<'PY'
|
|
738
|
+
from pathlib import Path
|
|
739
|
+
import json
|
|
740
|
+
import sys
|
|
741
|
+
root = Path(sys.argv[1])
|
|
742
|
+
assistant_name = sys.argv[2]
|
|
743
|
+
index_file = root / 'indexes' / 'assistants' / f'{assistant_name}.json'
|
|
744
|
+
if not index_file.exists():
|
|
745
|
+
raise SystemExit(0)
|
|
746
|
+
try:
|
|
747
|
+
index = json.loads(index_file.read_text())
|
|
748
|
+
except Exception:
|
|
749
|
+
raise SystemExit(0)
|
|
750
|
+
work_session_id = str(index.get('activeWorkSessionId') or '').strip()
|
|
751
|
+
if not work_session_id:
|
|
752
|
+
raise SystemExit(0)
|
|
753
|
+
meta_file = root / 'objects' / work_session_id / 'meta.json'
|
|
754
|
+
if not meta_file.exists():
|
|
755
|
+
raise SystemExit(0)
|
|
756
|
+
try:
|
|
757
|
+
meta = json.loads(meta_file.read_text())
|
|
758
|
+
except Exception:
|
|
759
|
+
raise SystemExit(0)
|
|
760
|
+
active_surface = str(meta.get('activeSurface') or '').strip()
|
|
761
|
+
archived_at = meta.get('archivedAt')
|
|
762
|
+
archived_text = '' if archived_at is None else str(archived_at).strip()
|
|
763
|
+
if not archived_text and active_surface in {'work_ally_channel', 'official_codex_cli'}:
|
|
764
|
+
sys.stdout.write(f'{active_surface}|{meta.get("workSessionId") or work_session_id}')
|
|
765
|
+
PY
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
assistant_work_session_migrate() {
|
|
769
|
+
local root="$1"
|
|
770
|
+
local old_name="$2"
|
|
771
|
+
local new_name="$3"
|
|
772
|
+
local old_codex_home="$4"
|
|
773
|
+
local new_codex_home="$5"
|
|
774
|
+
|
|
775
|
+
[ -d "$root" ] || return 0
|
|
776
|
+
work_ally_require_cmd python3
|
|
777
|
+
python3 - "$root" "$old_name" "$new_name" "$old_codex_home" "$new_codex_home" <<'PY'
|
|
778
|
+
from pathlib import Path
|
|
779
|
+
import json
|
|
780
|
+
import sys
|
|
781
|
+
root = Path(sys.argv[1])
|
|
782
|
+
old_name, new_name, old_codex_home, new_codex_home = sys.argv[2:6]
|
|
783
|
+
objects_dir = root / 'objects'
|
|
784
|
+
if objects_dir.exists():
|
|
785
|
+
for entry in objects_dir.iterdir():
|
|
786
|
+
if not entry.is_dir():
|
|
787
|
+
continue
|
|
788
|
+
meta_file = entry / 'meta.json'
|
|
789
|
+
if not meta_file.exists():
|
|
790
|
+
continue
|
|
791
|
+
try:
|
|
792
|
+
meta = json.loads(meta_file.read_text())
|
|
793
|
+
except Exception:
|
|
794
|
+
continue
|
|
795
|
+
changed = False
|
|
796
|
+
if meta.get('assistantName') == old_name:
|
|
797
|
+
meta['assistantName'] = new_name
|
|
798
|
+
changed = True
|
|
799
|
+
if meta.get('assistantCodexHome') == old_codex_home:
|
|
800
|
+
meta['assistantCodexHome'] = new_codex_home
|
|
801
|
+
changed = True
|
|
802
|
+
if changed:
|
|
803
|
+
meta_file.write_text(json.dumps(meta, indent=2) + '\n')
|
|
804
|
+
assistants_dir = root / 'indexes' / 'assistants'
|
|
805
|
+
old_index = assistants_dir / f'{old_name}.json'
|
|
806
|
+
new_index = assistants_dir / f'{new_name}.json'
|
|
807
|
+
if old_index.exists():
|
|
808
|
+
index = json.loads(old_index.read_text())
|
|
809
|
+
index['assistantName'] = new_name
|
|
810
|
+
new_index.parent.mkdir(parents=True, exist_ok=True)
|
|
811
|
+
new_index.write_text(json.dumps(index, indent=2) + '\n')
|
|
812
|
+
if new_index != old_index:
|
|
813
|
+
old_index.unlink()
|
|
814
|
+
PY
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
assistant_project_registry_rename_binding() {
|
|
818
|
+
local old_name="$1"
|
|
819
|
+
local new_name="$2"
|
|
820
|
+
local tmp_file
|
|
821
|
+
|
|
822
|
+
[ -f "$WORK_ALLY_PROJECT_REGISTRY_FILE" ] || return 0
|
|
823
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-project-registry.XXXXXX")
|
|
824
|
+
|
|
825
|
+
awk -v old_name="$old_name" -v new_name="$new_name" '
|
|
826
|
+
{
|
|
827
|
+
if ($0 == " assistant: " old_name) {
|
|
828
|
+
print " assistant: " new_name
|
|
829
|
+
next
|
|
830
|
+
}
|
|
831
|
+
print
|
|
832
|
+
}
|
|
833
|
+
' "$WORK_ALLY_PROJECT_REGISTRY_FILE" > "$tmp_file"
|
|
834
|
+
|
|
835
|
+
mv "$tmp_file" "$WORK_ALLY_PROJECT_REGISTRY_FILE"
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
assistant_registry_rename_entry() {
|
|
839
|
+
local old_name="$1"
|
|
840
|
+
local new_name="$2"
|
|
841
|
+
local new_home="$3"
|
|
842
|
+
local new_codex_home="$4"
|
|
843
|
+
local workspace_root="$5"
|
|
844
|
+
local description created_at
|
|
845
|
+
|
|
846
|
+
description=$(work_ally_assistant_description "$old_name")
|
|
847
|
+
created_at=$(work_ally_registry_field "$old_name" created_at || true)
|
|
848
|
+
|
|
849
|
+
work_ally_registry_remove_assistant "$old_name"
|
|
850
|
+
build_registry_entry "$new_name" "$new_home" "$new_codex_home" "$workspace_root" "$description" "$created_at" >> "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
assistant_lock_rewrite_name() {
|
|
854
|
+
local lock_file="$1"
|
|
855
|
+
local assistant_name="$2"
|
|
856
|
+
local tmp_file
|
|
857
|
+
|
|
858
|
+
[ -f "$lock_file" ] || return 0
|
|
859
|
+
tmp_file=$(mktemp "${TMPDIR:-/tmp}/work-ally-assistant-lock.XXXXXX")
|
|
860
|
+
|
|
861
|
+
awk -v assistant_name="$assistant_name" '
|
|
862
|
+
/^assistant=/ {
|
|
863
|
+
print "assistant=" assistant_name
|
|
864
|
+
next
|
|
865
|
+
}
|
|
866
|
+
{
|
|
867
|
+
print
|
|
868
|
+
}
|
|
869
|
+
' "$lock_file" > "$tmp_file"
|
|
870
|
+
|
|
871
|
+
mv "$tmp_file" "$lock_file"
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
bind_assistant() {
|
|
875
|
+
local name="${1:-}"
|
|
876
|
+
local workspace_root=""
|
|
877
|
+
local assistant_home codex_home description existing_workspace
|
|
878
|
+
|
|
879
|
+
shift || true
|
|
880
|
+
[ -n "$name" ] || work_ally_die "Assistant name is required"
|
|
881
|
+
work_ally_require_registered_assistant "$name"
|
|
882
|
+
|
|
883
|
+
while [ "$#" -gt 0 ]; do
|
|
884
|
+
case "$1" in
|
|
885
|
+
--workspace)
|
|
886
|
+
[ "$#" -ge 2 ] || work_ally_die "--workspace requires a value"
|
|
887
|
+
workspace_root="$2"
|
|
888
|
+
shift 2
|
|
889
|
+
;;
|
|
890
|
+
--workspace=*)
|
|
891
|
+
workspace_root="${1#--workspace=}"
|
|
892
|
+
shift
|
|
893
|
+
;;
|
|
894
|
+
*)
|
|
895
|
+
work_ally_die "Unknown argument: $1"
|
|
896
|
+
;;
|
|
897
|
+
esac
|
|
898
|
+
done
|
|
899
|
+
|
|
900
|
+
[ -n "$workspace_root" ] || work_ally_die "--workspace is required"
|
|
901
|
+
[ -d "$workspace_root" ] || work_ally_die "Workspace directory not found: $workspace_root"
|
|
902
|
+
workspace_root=$(cd "$workspace_root" && pwd)
|
|
903
|
+
|
|
904
|
+
assistant_home=$(work_ally_assistant_registered_home "$name")
|
|
905
|
+
codex_home=$(work_ally_assistant_registered_codex_home "$name")
|
|
906
|
+
description=$(work_ally_assistant_description "$name")
|
|
907
|
+
existing_workspace=$(work_ally_assistant_registered_workspace "$name" || true)
|
|
908
|
+
if [ -z "$existing_workspace" ]; then
|
|
909
|
+
existing_workspace=$(work_ally_registry_field "$name" workspace_root || true)
|
|
910
|
+
fi
|
|
911
|
+
|
|
912
|
+
if [ "$existing_workspace" = "$workspace_root" ]; then
|
|
913
|
+
work_ally_ok "Assistant $name 已经绑定到目标项目"
|
|
914
|
+
printf 'assistant_home: %s\n' "$assistant_home"
|
|
915
|
+
printf 'workspace: %s\n' "$workspace_root"
|
|
916
|
+
return 0
|
|
917
|
+
fi
|
|
918
|
+
|
|
919
|
+
if work_ally_assistant_is_busy "$name"; then
|
|
920
|
+
work_ally_die "Assistant $name is currently running. Stop it before rebinding."
|
|
921
|
+
fi
|
|
922
|
+
|
|
923
|
+
assistant_profile_remove_trusted_project "$codex_home/config.toml" "$existing_workspace"
|
|
924
|
+
assistant_profile_ensure_trusted_project "$codex_home/config.toml" "$workspace_root"
|
|
925
|
+
assistant_profile_rewrite_workspace_reference "$assistant_home/AGENTS.md" "$existing_workspace" "$workspace_root"
|
|
926
|
+
assistant_profile_rewrite_workspace_reference "$codex_home/AGENTS.md" "$existing_workspace" "$workspace_root"
|
|
927
|
+
assistant_registry_update_binding "$name" "$workspace_root"
|
|
928
|
+
work_ally_project_registry_set "$workspace_root" "$name"
|
|
929
|
+
work_ally_assistant_git_checkpoint "$assistant_home" "rebind assistant $name to $workspace_root" || true
|
|
930
|
+
|
|
931
|
+
work_ally_ok "Assistant binding updated for $name"
|
|
932
|
+
printf 'assistant_home: %s\n' "$assistant_home"
|
|
933
|
+
printf 'workspace: %s\n' "$workspace_root"
|
|
934
|
+
[ -n "$description" ] && printf 'description: %s\n' "$description"
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
list_assistants() {
|
|
938
|
+
if [ ! -f "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" ]; then
|
|
939
|
+
echo 'No assistants registered yet.'
|
|
940
|
+
return 0
|
|
941
|
+
fi
|
|
942
|
+
|
|
943
|
+
awk '
|
|
944
|
+
/^ [^[:space:]]+:$/ {
|
|
945
|
+
name=$0
|
|
946
|
+
sub(/^ /, "", name)
|
|
947
|
+
sub(/:$/, "", name)
|
|
948
|
+
current=name
|
|
949
|
+
names[++count]=name
|
|
950
|
+
next
|
|
951
|
+
}
|
|
952
|
+
current && /^ description: / {
|
|
953
|
+
value=$0
|
|
954
|
+
sub(/^ description: /, "", value)
|
|
955
|
+
descriptions[current]=value
|
|
956
|
+
next
|
|
957
|
+
}
|
|
958
|
+
current && /^ codex_home: / {
|
|
959
|
+
value=$0
|
|
960
|
+
sub(/^ codex_home: /, "", value)
|
|
961
|
+
codex[current]=value
|
|
962
|
+
next
|
|
963
|
+
}
|
|
964
|
+
END {
|
|
965
|
+
if (!count) {
|
|
966
|
+
print "No assistants registered yet."
|
|
967
|
+
exit 0
|
|
968
|
+
}
|
|
969
|
+
for (i=1; i<=count; i++) {
|
|
970
|
+
name=names[i]
|
|
971
|
+
printf "%s\n", name
|
|
972
|
+
if (descriptions[name] != "") printf " description: %s\n", descriptions[name]
|
|
973
|
+
if (codex[name] != "") printf " codex_home: %s\n", codex[name]
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
' "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
create_or_ensure_assistant() {
|
|
980
|
+
local mode="$1"
|
|
981
|
+
shift
|
|
982
|
+
local name="${1:-}"
|
|
983
|
+
shift || true
|
|
984
|
+
[ -n "$name" ] || work_ally_die "Assistant name is required"
|
|
985
|
+
work_ally_validate_assistant_name "$name"
|
|
986
|
+
|
|
987
|
+
local description="Assistant profile for $name"
|
|
988
|
+
local workspace_root=""
|
|
989
|
+
local existing_workspace=""
|
|
990
|
+
local git_remote=""
|
|
991
|
+
while [ "$#" -gt 0 ]; do
|
|
992
|
+
case "$1" in
|
|
993
|
+
--description)
|
|
994
|
+
[ "$#" -ge 2 ] || work_ally_die "--description requires a value"
|
|
995
|
+
description="$2"
|
|
996
|
+
shift 2
|
|
997
|
+
;;
|
|
998
|
+
--description=*)
|
|
999
|
+
description="${1#--description=}"
|
|
1000
|
+
shift
|
|
1001
|
+
;;
|
|
1002
|
+
--workspace)
|
|
1003
|
+
[ "$#" -ge 2 ] || work_ally_die "--workspace requires a value"
|
|
1004
|
+
workspace_root="$2"
|
|
1005
|
+
shift 2
|
|
1006
|
+
;;
|
|
1007
|
+
--workspace=*)
|
|
1008
|
+
workspace_root="${1#--workspace=}"
|
|
1009
|
+
shift
|
|
1010
|
+
;;
|
|
1011
|
+
--git-remote)
|
|
1012
|
+
[ "$#" -ge 2 ] || work_ally_die "--git-remote requires a value"
|
|
1013
|
+
git_remote="$2"
|
|
1014
|
+
shift 2
|
|
1015
|
+
;;
|
|
1016
|
+
--git-remote=*)
|
|
1017
|
+
git_remote="${1#--git-remote=}"
|
|
1018
|
+
shift
|
|
1019
|
+
;;
|
|
1020
|
+
*)
|
|
1021
|
+
work_ally_die "Unknown argument: $1"
|
|
1022
|
+
;;
|
|
1023
|
+
esac
|
|
1024
|
+
done
|
|
1025
|
+
|
|
1026
|
+
[ -n "$workspace_root" ] || work_ally_die "--workspace is required"
|
|
1027
|
+
[ -d "$workspace_root" ] || work_ally_die "Workspace directory not found: $workspace_root"
|
|
1028
|
+
workspace_root=$(cd "$workspace_root" && pwd)
|
|
1029
|
+
export WORK_ALLY_WORKSPACE_ROOT="$workspace_root"
|
|
1030
|
+
|
|
1031
|
+
if work_ally_registry_has_assistant "$name"; then
|
|
1032
|
+
existing_workspace=$(work_ally_assistant_registered_workspace "$name" || true)
|
|
1033
|
+
if [ -n "$existing_workspace" ] && [ "$existing_workspace" != "$workspace_root" ]; then
|
|
1034
|
+
work_ally_die "Assistant $name is already bound to $existing_workspace. Create another assistant for $workspace_root."
|
|
1035
|
+
fi
|
|
1036
|
+
fi
|
|
1037
|
+
|
|
1038
|
+
if [ "$mode" = add ] && work_ally_registry_has_assistant "$name"; then
|
|
1039
|
+
work_ally_die "Assistant already registered: $name"
|
|
1040
|
+
fi
|
|
1041
|
+
|
|
1042
|
+
local homes assistant_home codex_home
|
|
1043
|
+
homes=$(bootstrap_assistant "$name" "$workspace_root" "$description" "$git_remote" "$existing_workspace")
|
|
1044
|
+
assistant_home=$(printf '%s\n' "$homes" | sed -n '1p')
|
|
1045
|
+
codex_home=$(printf '%s\n' "$homes" | sed -n '2p')
|
|
1046
|
+
ensure_registry_entry "$name" "$assistant_home" "$codex_home" "$workspace_root" "$description"
|
|
1047
|
+
|
|
1048
|
+
work_ally_ok "Assistant desk ready for $name"
|
|
1049
|
+
printf 'assistant_home: %s\n' "$assistant_home"
|
|
1050
|
+
printf 'codex_home: %s\n' "$codex_home"
|
|
1051
|
+
printf 'workspace: %s\n' "$workspace_root"
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
show_assistant() {
|
|
1055
|
+
local name="${1:-}"
|
|
1056
|
+
local assistant_home
|
|
1057
|
+
[ -n "$name" ] || work_ally_die "Assistant name is required"
|
|
1058
|
+
work_ally_require_registered_assistant "$name"
|
|
1059
|
+
assistant_home=$(work_ally_assistant_registered_home "$name")
|
|
1060
|
+
printf 'name: %s\n' "$name"
|
|
1061
|
+
printf 'assistant_home: %s\n' "$assistant_home"
|
|
1062
|
+
printf 'codex_home: %s\n' "$(work_ally_assistant_registered_codex_home "$name")"
|
|
1063
|
+
printf 'workspace: %s\n' "$(work_ally_assistant_registered_workspace "$name")"
|
|
1064
|
+
printf 'description: %s\n' "$(work_ally_assistant_description "$name")"
|
|
1065
|
+
printf 'git_remote: %s\n' "$(work_ally_assistant_git_origin_url "$assistant_home" || true)"
|
|
1066
|
+
printf 'git_branch: %s\n' "$(work_ally_assistant_git_branch "$assistant_home")"
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
confirm_assistant_removal() {
|
|
1070
|
+
local name="$1"
|
|
1071
|
+
local assistant_home="$2"
|
|
1072
|
+
local attempt reply
|
|
1073
|
+
|
|
1074
|
+
printf 'Danger: this will permanently delete assistant %s.\n' "$name" >&2
|
|
1075
|
+
printf 'Desk: %s\n' "$assistant_home" >&2
|
|
1076
|
+
printf 'It will also remove all project-to-assistant mappings for this assistant.\n' >&2
|
|
1077
|
+
|
|
1078
|
+
for attempt in 1 2 3; do
|
|
1079
|
+
printf 'Type YES to continue (%s/3): ' "$attempt" >&2
|
|
1080
|
+
if ! IFS= read -r reply; then
|
|
1081
|
+
work_ally_die "Assistant removal aborted"
|
|
1082
|
+
fi
|
|
1083
|
+
if [ "$reply" != "YES" ]; then
|
|
1084
|
+
work_ally_die "Assistant removal aborted"
|
|
1085
|
+
fi
|
|
1086
|
+
done
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
remove_assistant() {
|
|
1090
|
+
local name="${1:-}"
|
|
1091
|
+
local assistant_home lock_file lock_workspace
|
|
1092
|
+
[ -n "$name" ] || work_ally_die "Assistant name is required"
|
|
1093
|
+
work_ally_require_registered_assistant "$name"
|
|
1094
|
+
assistant_home=$(work_ally_assistant_registered_home "$name")
|
|
1095
|
+
[ -n "$assistant_home" ] || work_ally_die "Assistant profile is invalid: missing assistant_home for $name"
|
|
1096
|
+
|
|
1097
|
+
if work_ally_assistant_is_busy "$name"; then
|
|
1098
|
+
lock_file=$(work_ally_assistant_lock_file "$name")
|
|
1099
|
+
lock_workspace=$(work_ally_assistant_lock_field "$lock_file" workspace_root || true)
|
|
1100
|
+
work_ally_die "Assistant $name is currently running${lock_workspace:+ in project $lock_workspace}. Stop it before removing."
|
|
1101
|
+
fi
|
|
1102
|
+
|
|
1103
|
+
confirm_assistant_removal "$name" "$assistant_home"
|
|
1104
|
+
|
|
1105
|
+
work_ally_project_registry_remove_assistant "$name"
|
|
1106
|
+
work_ally_registry_remove_assistant "$name"
|
|
1107
|
+
|
|
1108
|
+
lock_file=$(work_ally_assistant_lock_file "$name")
|
|
1109
|
+
rm -f "$lock_file"
|
|
1110
|
+
rm -rf "$assistant_home"
|
|
1111
|
+
|
|
1112
|
+
unset WORK_ALLY_ASSISTANT_HOME WORK_ALLY_STATE_DIR WORK_ALLY_RUNTIME_DIR \
|
|
1113
|
+
WORK_ALLY_LOG_DIR WORK_ALLY_SESSION_DIR WORK_ALLY_RUN_DIR \
|
|
1114
|
+
WORK_ALLY_CACHE_DIR WORK_ALLY_ENV_FILE WORK_ALLY_ASSISTANT_STATE_FILE
|
|
1115
|
+
printf '✓ Assistant removed: %s\n' "$name"
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
rename_assistant_apply() {
|
|
1119
|
+
local old_name="$1"
|
|
1120
|
+
local new_name="$2"
|
|
1121
|
+
local old_home="$3"
|
|
1122
|
+
local new_home="$4"
|
|
1123
|
+
local old_codex_home="$5"
|
|
1124
|
+
local new_codex_home="$6"
|
|
1125
|
+
local workspace_root="$7"
|
|
1126
|
+
local old_lock="$8"
|
|
1127
|
+
local new_lock="$9"
|
|
1128
|
+
local new_state_dir="$new_home/.system"
|
|
1129
|
+
local config_file="$new_codex_home/config.toml"
|
|
1130
|
+
|
|
1131
|
+
mv "$old_home" "$new_home" || return 1
|
|
1132
|
+
|
|
1133
|
+
assistant_profile_rewrite_workspace_reference "$new_home/AGENTS.md" "$old_home" "$new_home" || return 1
|
|
1134
|
+
assistant_profile_rewrite_workspace_reference "$new_home/.system/codex-home/AGENTS.md" "$old_home" "$new_home" || return 1
|
|
1135
|
+
assistant_profile_rewrite_workspace_reference "$config_file" "$old_home" "$new_home" || return 1
|
|
1136
|
+
assistant_profile_remove_trusted_project "$config_file" "$old_home" || return 1
|
|
1137
|
+
assistant_profile_ensure_trusted_project "$config_file" "$new_home" || return 1
|
|
1138
|
+
assistant_profile_ensure_runtime_defaults "$config_file" "$new_home" || return 1
|
|
1139
|
+
assistant_profile_rewrite_soul_name "$new_home/SOUL.md" "$old_name" "$new_name" || return 1
|
|
1140
|
+
assistant_profile_rewrite_managed_env "$new_home/.system/config.env" "$new_name" "$new_home" "$new_codex_home" "$new_state_dir" || return 1
|
|
1141
|
+
assistant_profile_rewrite_managed_env "$new_home/.system/runtime/assistant.env" "$new_name" "$new_home" "$new_codex_home" "$new_state_dir" || return 1
|
|
1142
|
+
assistant_work_session_migrate "$(assistant_work_sessions_root "$new_home")" "$old_name" "$new_name" "$old_codex_home" "$new_codex_home" || return 1
|
|
1143
|
+
assistant_project_registry_rename_binding "$old_name" "$new_name" || return 1
|
|
1144
|
+
assistant_registry_rename_entry "$old_name" "$new_name" "$new_home" "$new_codex_home" "$workspace_root" || return 1
|
|
1145
|
+
|
|
1146
|
+
if [ -f "$old_lock" ]; then
|
|
1147
|
+
mv "$old_lock" "$new_lock" || return 1
|
|
1148
|
+
assistant_lock_rewrite_name "$new_lock" "$new_name" || return 1
|
|
1149
|
+
fi
|
|
1150
|
+
|
|
1151
|
+
return 0
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
rename_assistant_restore_from_backup() {
|
|
1155
|
+
local backup_dir="$1"
|
|
1156
|
+
local old_home="$2"
|
|
1157
|
+
local new_home="$3"
|
|
1158
|
+
local old_lock="$4"
|
|
1159
|
+
local new_lock="$5"
|
|
1160
|
+
|
|
1161
|
+
rm -f "$new_lock"
|
|
1162
|
+
|
|
1163
|
+
if [ -d "$new_home" ]; then
|
|
1164
|
+
rm -rf "$new_home"
|
|
1165
|
+
fi
|
|
1166
|
+
|
|
1167
|
+
if [ -f "$backup_dir/assistant-registry.yaml" ]; then
|
|
1168
|
+
mkdir -p "$(dirname "$WORK_ALLY_ASSISTANT_REGISTRY_FILE")"
|
|
1169
|
+
cp "$backup_dir/assistant-registry.yaml" "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
1170
|
+
else
|
|
1171
|
+
rm -f "$WORK_ALLY_ASSISTANT_REGISTRY_FILE"
|
|
1172
|
+
fi
|
|
1173
|
+
|
|
1174
|
+
if [ -f "$backup_dir/project-registry.yaml" ]; then
|
|
1175
|
+
mkdir -p "$(dirname "$WORK_ALLY_PROJECT_REGISTRY_FILE")"
|
|
1176
|
+
cp "$backup_dir/project-registry.yaml" "$WORK_ALLY_PROJECT_REGISTRY_FILE"
|
|
1177
|
+
else
|
|
1178
|
+
rm -f "$WORK_ALLY_PROJECT_REGISTRY_FILE"
|
|
1179
|
+
fi
|
|
1180
|
+
|
|
1181
|
+
if [ -f "$backup_dir/assistant.lock" ]; then
|
|
1182
|
+
mkdir -p "$(dirname "$old_lock")"
|
|
1183
|
+
cp "$backup_dir/assistant.lock" "$old_lock"
|
|
1184
|
+
else
|
|
1185
|
+
rm -f "$old_lock"
|
|
1186
|
+
fi
|
|
1187
|
+
|
|
1188
|
+
if [ ! -d "$old_home" ] && [ -d "$backup_dir/assistant-home" ]; then
|
|
1189
|
+
mv "$backup_dir/assistant-home" "$old_home" || return 1
|
|
1190
|
+
fi
|
|
1191
|
+
|
|
1192
|
+
return 0
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
rename_assistant() {
|
|
1196
|
+
local old_name="${1:-}"
|
|
1197
|
+
local new_name="${2:-}"
|
|
1198
|
+
local old_home old_codex_home expected_old_home expected_old_codex_home
|
|
1199
|
+
local new_home new_codex_home workspace_root old_lock new_lock new_work_session_index
|
|
1200
|
+
local lock_workspace blocking_summary active_surface work_session_id backup_dir
|
|
1201
|
+
|
|
1202
|
+
[ -n "$old_name" ] || work_ally_die "Old assistant name is required"
|
|
1203
|
+
[ -n "$new_name" ] || work_ally_die "New assistant name is required"
|
|
1204
|
+
shift 2 || true
|
|
1205
|
+
[ "$#" -eq 0 ] || work_ally_die "Unknown argument: $1"
|
|
1206
|
+
|
|
1207
|
+
work_ally_require_registered_assistant "$old_name"
|
|
1208
|
+
work_ally_validate_assistant_name "$new_name"
|
|
1209
|
+
[ "$old_name" != "$new_name" ] || work_ally_die "Old and new assistant names must differ"
|
|
1210
|
+
|
|
1211
|
+
if work_ally_registry_has_assistant "$new_name"; then
|
|
1212
|
+
work_ally_die "Assistant already registered: $new_name"
|
|
1213
|
+
fi
|
|
1214
|
+
|
|
1215
|
+
old_home=$(work_ally_assistant_registered_home "$old_name")
|
|
1216
|
+
old_codex_home=$(work_ally_assistant_registered_codex_home "$old_name")
|
|
1217
|
+
workspace_root=$(work_ally_assistant_registered_workspace "$old_name" || true)
|
|
1218
|
+
if [ -z "$workspace_root" ]; then
|
|
1219
|
+
workspace_root=$(work_ally_registry_field "$old_name" workspace_root || true)
|
|
1220
|
+
fi
|
|
1221
|
+
|
|
1222
|
+
expected_old_home=$(work_ally_assistant_home "$old_name")
|
|
1223
|
+
expected_old_codex_home=$(work_ally_assistant_codex_home "$old_name")
|
|
1224
|
+
[ -n "$old_home" ] || work_ally_die "Assistant profile is invalid: missing assistant_home for $old_name"
|
|
1225
|
+
[ -n "$old_codex_home" ] || work_ally_die "Assistant profile is invalid: missing codex_home for $old_name"
|
|
1226
|
+
[ -n "$workspace_root" ] || work_ally_die "Assistant $old_name is not bound to a project yet."
|
|
1227
|
+
[ "$old_home" = "$expected_old_home" ] || work_ally_die "Assistant $old_name cannot be renamed safely because assistant_home is not canonical: $old_home"
|
|
1228
|
+
[ "$old_codex_home" = "$expected_old_codex_home" ] || work_ally_die "Assistant $old_name cannot be renamed safely because codex_home is not canonical: $old_codex_home"
|
|
1229
|
+
[ -d "$old_home" ] || work_ally_die "Assistant profile is incomplete: missing assistant_home at $old_home"
|
|
1230
|
+
[ -d "$old_codex_home" ] || work_ally_die "Assistant profile is incomplete: missing codex_home at $old_codex_home"
|
|
1231
|
+
|
|
1232
|
+
new_home=$(work_ally_assistant_home "$new_name")
|
|
1233
|
+
new_codex_home=$(work_ally_assistant_codex_home "$new_name")
|
|
1234
|
+
[ ! -e "$new_home" ] || work_ally_die "Assistant home already exists for $new_name: $new_home"
|
|
1235
|
+
|
|
1236
|
+
new_work_session_index="$(assistant_work_sessions_root "$old_home")/indexes/assistants/$new_name.json"
|
|
1237
|
+
[ ! -e "$new_work_session_index" ] || work_ally_die "Assistant work-session index already exists for $new_name: $new_work_session_index"
|
|
1238
|
+
|
|
1239
|
+
old_lock=$(work_ally_assistant_lock_file "$old_name")
|
|
1240
|
+
new_lock=$(work_ally_assistant_lock_file "$new_name")
|
|
1241
|
+
[ ! -e "$new_lock" ] || work_ally_die "Assistant lock already exists for $new_name: $new_lock"
|
|
1242
|
+
|
|
1243
|
+
if work_ally_assistant_is_busy "$old_name"; then
|
|
1244
|
+
lock_workspace=$(work_ally_assistant_lock_field "$old_lock" workspace_root || true)
|
|
1245
|
+
work_ally_die "Assistant $old_name is currently running${lock_workspace:+ in project $lock_workspace}. Stop it before renaming."
|
|
1246
|
+
fi
|
|
1247
|
+
|
|
1248
|
+
blocking_summary=$(assistant_work_session_blocking_summary "$(assistant_work_sessions_root "$old_home")" "$old_name" || true)
|
|
1249
|
+
if [ -n "$blocking_summary" ]; then
|
|
1250
|
+
active_surface=${blocking_summary%%|*}
|
|
1251
|
+
work_session_id=${blocking_summary#*|}
|
|
1252
|
+
work_ally_die "Assistant $old_name still has an active work session on $active_surface ($work_session_id). Close or idle it before renaming."
|
|
1253
|
+
fi
|
|
1254
|
+
|
|
1255
|
+
backup_dir=$(mktemp -d "${TMPDIR:-/tmp}/work-ally-assistant-rename.XXXXXX")
|
|
1256
|
+
cp -R "$old_home" "$backup_dir/assistant-home" || work_ally_die "Failed to stage assistant backup for $old_name"
|
|
1257
|
+
[ ! -f "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" ] || cp "$WORK_ALLY_ASSISTANT_REGISTRY_FILE" "$backup_dir/assistant-registry.yaml"
|
|
1258
|
+
[ ! -f "$WORK_ALLY_PROJECT_REGISTRY_FILE" ] || cp "$WORK_ALLY_PROJECT_REGISTRY_FILE" "$backup_dir/project-registry.yaml"
|
|
1259
|
+
[ ! -f "$old_lock" ] || cp "$old_lock" "$backup_dir/assistant.lock"
|
|
1260
|
+
|
|
1261
|
+
if ! rename_assistant_apply "$old_name" "$new_name" "$old_home" "$new_home" "$old_codex_home" "$new_codex_home" "$workspace_root" "$old_lock" "$new_lock"; then
|
|
1262
|
+
if ! rename_assistant_restore_from_backup "$backup_dir" "$old_home" "$new_home" "$old_lock" "$new_lock"; then
|
|
1263
|
+
rm -rf "$backup_dir"
|
|
1264
|
+
work_ally_die "Assistant rename failed and rollback failed. Inspect $old_home, $new_home and the registries manually."
|
|
1265
|
+
fi
|
|
1266
|
+
rm -rf "$backup_dir"
|
|
1267
|
+
work_ally_die "Assistant rename from $old_name to $new_name failed. Original state was restored."
|
|
1268
|
+
fi
|
|
1269
|
+
|
|
1270
|
+
rm -rf "$backup_dir"
|
|
1271
|
+
|
|
1272
|
+
export WORK_ALLY_WORKSPACE_ROOT="$workspace_root"
|
|
1273
|
+
work_ally_hydrate_assistant_context "$new_name"
|
|
1274
|
+
work_ally_assistant_git_checkpoint "$new_home" "rename assistant $old_name to $new_name" || true
|
|
1275
|
+
|
|
1276
|
+
work_ally_ok "Assistant renamed from $old_name to $new_name"
|
|
1277
|
+
printf 'old_name: %s\n' "$old_name"
|
|
1278
|
+
printf 'new_name: %s\n' "$new_name"
|
|
1279
|
+
printf 'assistant_home: %s\n' "$new_home"
|
|
1280
|
+
printf 'codex_home: %s\n' "$new_codex_home"
|
|
1281
|
+
printf 'workspace: %s\n' "$workspace_root"
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
case "$SUBCMD" in
|
|
1285
|
+
add)
|
|
1286
|
+
create_or_ensure_assistant add "$@"
|
|
1287
|
+
;;
|
|
1288
|
+
ensure)
|
|
1289
|
+
create_or_ensure_assistant ensure "$@"
|
|
1290
|
+
;;
|
|
1291
|
+
bind)
|
|
1292
|
+
bind_assistant "$@"
|
|
1293
|
+
;;
|
|
1294
|
+
remove)
|
|
1295
|
+
remove_assistant "$@"
|
|
1296
|
+
;;
|
|
1297
|
+
rename)
|
|
1298
|
+
rename_assistant "$@"
|
|
1299
|
+
;;
|
|
1300
|
+
list)
|
|
1301
|
+
list_assistants
|
|
1302
|
+
;;
|
|
1303
|
+
show)
|
|
1304
|
+
show_assistant "$@"
|
|
1305
|
+
;;
|
|
1306
|
+
help|-h|--help)
|
|
1307
|
+
usage
|
|
1308
|
+
;;
|
|
1309
|
+
*)
|
|
1310
|
+
work_ally_die "Unknown assistant subcommand: $SUBCMD"
|
|
1311
|
+
;;
|
|
1312
|
+
esac
|