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.
Files changed (172) hide show
  1. package/AGENTS.md +110 -0
  2. package/DASHBOARD.md +160 -0
  3. package/PRODUCT.md +113 -0
  4. package/README.md +403 -0
  5. package/ally.sh +171 -0
  6. package/bridge/src/approval-rules.ts +360 -0
  7. package/bridge/src/channel-delivery.ts +207 -0
  8. package/bridge/src/channel-types.ts +22 -0
  9. package/bridge/src/channels/fake/adapter.ts +31 -0
  10. package/bridge/src/channels/feishu/adapter.ts +411 -0
  11. package/bridge/src/channels/feishu/approvals.ts +6 -0
  12. package/bridge/src/channels/feishu/formatter.ts +276 -0
  13. package/bridge/src/channels/feishu/normalize.ts +368 -0
  14. package/bridge/src/codex-config.ts +52 -0
  15. package/bridge/src/config.ts +240 -0
  16. package/bridge/src/fake-runtime-client.ts +505 -0
  17. package/bridge/src/handoff-service.ts +494 -0
  18. package/bridge/src/logger.ts +194 -0
  19. package/bridge/src/memory-digest.ts +186 -0
  20. package/bridge/src/receiver-approval-autonomy.ts +158 -0
  21. package/bridge/src/receiver-control-core.ts +140 -0
  22. package/bridge/src/receiver-control-work-session.ts +218 -0
  23. package/bridge/src/receiver-control.ts +83 -0
  24. package/bridge/src/receiver-delivery.ts +136 -0
  25. package/bridge/src/receiver-helpers.ts +96 -0
  26. package/bridge/src/receiver-human-gate.ts +333 -0
  27. package/bridge/src/receiver-inbound-preflight.ts +162 -0
  28. package/bridge/src/receiver-recovery.ts +236 -0
  29. package/bridge/src/receiver-runtime-callbacks.ts +367 -0
  30. package/bridge/src/receiver-runtime-policy.ts +132 -0
  31. package/bridge/src/receiver-runtime-state.ts +124 -0
  32. package/bridge/src/receiver-support-actions.ts +189 -0
  33. package/bridge/src/receiver-thread-start.ts +57 -0
  34. package/bridge/src/receiver-turn-coordination.ts +94 -0
  35. package/bridge/src/receiver-turn-execution.ts +257 -0
  36. package/bridge/src/receiver-turn-failure.ts +143 -0
  37. package/bridge/src/receiver-turn-result.ts +185 -0
  38. package/bridge/src/receiver-turn-steer.ts +70 -0
  39. package/bridge/src/receiver-work-session.ts +76 -0
  40. package/bridge/src/receiver.ts +329 -0
  41. package/bridge/src/router.ts +62 -0
  42. package/bridge/src/runtime-client-agent-messages.ts +150 -0
  43. package/bridge/src/runtime-client-message-dispatch.ts +176 -0
  44. package/bridge/src/runtime-client-protocol.ts +411 -0
  45. package/bridge/src/runtime-client-request-ops.ts +56 -0
  46. package/bridge/src/runtime-client-run-turn.ts +158 -0
  47. package/bridge/src/runtime-client-thread-ops.ts +270 -0
  48. package/bridge/src/runtime-client-transport.ts +309 -0
  49. package/bridge/src/runtime-client-turn-poll.ts +224 -0
  50. package/bridge/src/runtime-client-turn-read.ts +185 -0
  51. package/bridge/src/runtime-client-turn-state.ts +105 -0
  52. package/bridge/src/runtime-client.ts +344 -0
  53. package/bridge/src/runtime-user-input.ts +403 -0
  54. package/bridge/src/scheduler.ts +239 -0
  55. package/bridge/src/server-handoff-command.ts +364 -0
  56. package/bridge/src/server-main.ts +80 -0
  57. package/bridge/src/server-routine-command.ts +60 -0
  58. package/bridge/src/server-routine-execution.ts +222 -0
  59. package/bridge/src/server-runtime-app-support.ts +107 -0
  60. package/bridge/src/server-runtime-app.ts +238 -0
  61. package/bridge/src/server-thread-sync-command.ts +63 -0
  62. package/bridge/src/server.ts +17 -0
  63. package/bridge/src/session-store-delivery.ts +220 -0
  64. package/bridge/src/session-store-human-gate.ts +380 -0
  65. package/bridge/src/session-store-inbound-acceptance.ts +66 -0
  66. package/bridge/src/session-store-meta.ts +134 -0
  67. package/bridge/src/session-store-turn-ledger.ts +272 -0
  68. package/bridge/src/session-store.ts +380 -0
  69. package/bridge/src/system-notify.ts +220 -0
  70. package/bridge/src/thread-sync.ts +200 -0
  71. package/bridge/src/translator.ts +494 -0
  72. package/bridge/src/types.ts +289 -0
  73. package/bridge/src/utils.ts +104 -0
  74. package/bridge/src/work-session-store.ts +471 -0
  75. package/docs/.gitkeep +0 -0
  76. package/docs/architecture/codex-feishu-bridge-proposal.md +2742 -0
  77. package/docs/completed/FEATURE-feishu-markdown-and-reply-support.md +327 -0
  78. package/docs/completed/README.md +21 -0
  79. package/docs/completed/SPEC-approval-autonomy-and-safe-defaults.md +205 -0
  80. package/docs/completed/SPEC-approval-batch-and-strict-reply-shortcuts.md +153 -0
  81. package/docs/completed/SPEC-conversation-noise-reduction-and-busy-input-gate.md +538 -0
  82. package/docs/completed/SPEC-engineering-sop-skillization.md +190 -0
  83. package/docs/completed/SPEC-faithful-bridge-core-thinning-v2.md +376 -0
  84. package/docs/completed/SPEC-faithful-bridge-core-thinning.md +1071 -0
  85. package/docs/completed/SPEC-group-chat-sender-identity.md +301 -0
  86. package/docs/completed/SPEC-middleware-exception-visibility.md +227 -0
  87. package/docs/completed/SPEC-nightly-memory-digest-visibility.md +121 -0
  88. package/docs/completed/SPEC-project-group-chat-human-centered-conversation-mapping.md +326 -0
  89. package/docs/completed/SPEC-remove-cli-persona-bootstrap.md +201 -0
  90. package/docs/developer-workflow.md +49 -0
  91. package/docs/implementation/SPEC-codex-same-machine-session-handoff-implementation.md +239 -0
  92. package/docs/implementation/test-coverage-map.md +363 -0
  93. package/docs/implementation/work-ally-implementation-guide.md +790 -0
  94. package/docs/issues/README.md +10 -0
  95. package/docs/issues/pending/ANALYSIS-ally-premature-recovery-notice-and-task-state-semantics-2026-03-18.md +295 -0
  96. package/docs/issues/resolved/ANALYSIS-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +466 -0
  97. package/docs/issues/resolved/ANALYSIS-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +261 -0
  98. package/docs/issues/resolved/ANALYSIS-codex-app-server-transport-disconnect-semantics-2026-03-14.md +606 -0
  99. package/docs/issues/resolved/ANALYSIS-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +348 -0
  100. package/docs/issues/resolved/ANALYSIS-runtime-turn-delivery-and-recovery-2026-03-14.md +603 -0
  101. package/docs/issues/resolved/ANALYSIS-self-test-gap-approval-waiting-visible-but-approval-artifact-missing-2026-03-16.md +166 -0
  102. package/docs/issues/resolved/ANALYSIS-self-test-gap-blocking-state-visible-without-user-actionable-artifact-2026-03-16.md +186 -0
  103. package/docs/issues/resolved/ANALYSIS-self-test-gap-premature-terminalization-on-fresh-thread-poll-and-object-error-leak-2026-03-16.md +166 -0
  104. package/docs/issues/resolved/REPORT-ally-runtime-turn-delivery-3b42fb8-2026-03-15.md +373 -0
  105. package/docs/manual-acceptance.md +127 -0
  106. package/docs/ops-runbook.md +44 -0
  107. package/docs/planning/FEATURE-memory-system.md +748 -0
  108. package/docs/planning/SPEC-active-turn-steer-and-context-compaction-visibility.md +269 -0
  109. package/docs/planning/SPEC-approval-rules-inheritance-and-local-validation-lane.md +450 -0
  110. package/docs/planning/SPEC-assistant-persona-bootstrap.md +199 -0
  111. package/docs/planning/SPEC-assistant-rename.md +610 -0
  112. package/docs/planning/SPEC-bridge-app-server-protocol-alignment.md +667 -0
  113. package/docs/planning/SPEC-claude-runtime-host-for-work-ally.md +434 -0
  114. package/docs/planning/SPEC-cli-feishu-codex-session-unification.md +236 -0
  115. package/docs/planning/SPEC-codex-same-machine-session-handoff.md +873 -0
  116. package/docs/planning/SPEC-feishu-reaction-shortcuts.md +282 -0
  117. package/docs/planning/SPEC-local-stable-release-boundary.md +166 -0
  118. package/docs/planning/SPEC-managed-thread-entry-and-surface-mobility.md +862 -0
  119. package/docs/planning/SPEC-minimal-bridge-semantics-and-user-visible-surface.md +362 -0
  120. package/docs/planning/SPEC-npm-alpha-distribution-and-install-first-release.md +222 -0
  121. package/docs/planning/SPEC-remove-websocket-runtime-transport.md +364 -0
  122. package/docs/planning/SPEC-runtime-abstraction-phase-1.md +424 -0
  123. package/docs/planning/SPEC-runtime-connection-and-turn-recovery-semantics.md +274 -0
  124. package/docs/planning/SPEC-session-presence-and-state-visibility.md +397 -0
  125. package/docs/planning/SPEC-skill-first-capability-packaging.md +338 -0
  126. package/docs/planning/SPEC-stable-archive-contract.md +456 -0
  127. package/docs/planning/SPEC-supervised-start-boundary.md +127 -0
  128. package/docs/planning/SPEC-user-barrier-reduction-and-activation.md +832 -0
  129. package/docs/planning/ally-next.md +1278 -0
  130. package/docs/planning/assistant-workbench-spec.md +725 -0
  131. package/docs/planning/product-workbench.md +283 -0
  132. package/docs/product-onboarding.md +227 -0
  133. package/docs/product-spec-standard.md +528 -0
  134. package/docs/troubleshooting.md +45 -0
  135. package/docs/user-quickstart.md +46 -0
  136. package/internal/dispatch.sh +95 -0
  137. package/internal/lib/common.sh +1450 -0
  138. package/internal/modules/assistant/manage.sh +1312 -0
  139. package/internal/modules/bootstrap/setup.sh +144 -0
  140. package/internal/modules/config/init-env.sh +10 -0
  141. package/internal/modules/global/manage.sh +154 -0
  142. package/internal/modules/handoff/manage.sh +54 -0
  143. package/internal/modules/mcp/manage.sh +83 -0
  144. package/internal/modules/ops/logs.sh +76 -0
  145. package/internal/modules/routines/manage.sh +55 -0
  146. package/internal/modules/runtime/assistant-autosave.sh +26 -0
  147. package/internal/modules/runtime/restart.sh +6 -0
  148. package/internal/modules/runtime/start.sh +283 -0
  149. package/internal/modules/runtime/status.sh +194 -0
  150. package/internal/modules/runtime/stop.sh +55 -0
  151. package/internal/modules/runtime/supervisor.sh +216 -0
  152. package/internal/modules/runtime/update.sh +26 -0
  153. package/package.json +41 -0
  154. package/runtime/config/.gitkeep +0 -0
  155. package/runtime/host/.gitkeep +0 -0
  156. package/runtime/host/healthcheck-codex-app-server.ts +22 -0
  157. package/runtime/host/ping-pong-codex-app-server.ts +66 -0
  158. package/runtime/host/probe-codex-app-server.ts +115 -0
  159. package/skills/archive-reader/SKILL.md +9 -0
  160. package/skills/feishu-production-debug/SKILL.md +37 -0
  161. package/skills/feishu-production-debug/references/feishu-debug-order.md +49 -0
  162. package/skills/feishu-production-debug/references/platform-permission-baseline.md +23 -0
  163. package/skills/issue-to-spec-triage/SKILL.md +44 -0
  164. package/skills/issue-to-spec-triage/references/triage-rules.md +66 -0
  165. package/skills/memory-digest/SKILL.md +9 -0
  166. package/skills/post-implementation-closure/SKILL.md +39 -0
  167. package/skills/post-implementation-closure/references/closure-checklist.md +45 -0
  168. package/skills/post-implementation-closure/references/doc-drift-map.md +49 -0
  169. package/skills/product-spec/SKILL.md +244 -0
  170. package/templates/env.example +5 -0
  171. package/templates/routines/nightly-memory-digest.yaml +10 -0
  172. package/templates/workspace/AGENTS.md +26 -0
@@ -0,0 +1,216 @@
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
+ if [ -z "${WORK_ALLY_ASSISTANT_NAME:-}" ]; then
9
+ WORK_ALLY_ASSISTANT_NAME=$(work_ally_resolve_assistant_name_from_args "$@" || true)
10
+ fi
11
+ if [ -n "${WORK_ALLY_ASSISTANT_NAME:-}" ]; then
12
+ work_ally_sync_workspace_root_from_assistant "$WORK_ALLY_ASSISTANT_NAME"
13
+ work_ally_hydrate_assistant_context "$WORK_ALLY_ASSISTANT_NAME"
14
+ fi
15
+ work_ally_ensure_state_dirs
16
+ work_ally_load_env
17
+ work_ally_load_assistant_state || true
18
+
19
+ CHANNEL_IMPL="${WORK_ALLY_CHANNEL_IMPL:-feishu}"
20
+ RUNTIME_MODE="${WORK_ALLY_RUNTIME_MODE:-codex}"
21
+ ASSISTANT_GIT_REMOTE="${WORK_ALLY_ASSISTANT_GIT_REMOTE:-}"
22
+ SUPERVISOR_PID_FILE=$(work_ally_pid_file supervisor)
23
+ BRIDGE_PID_FILE=$(work_ally_pid_file bridge)
24
+ AUTOSAVE_PID_FILE=$(work_ally_pid_file assistant-autosave)
25
+ BRIDGE_LOG=$(work_ally_log_file_for bridge)
26
+ AUTOSAVE_LOG=$(work_ally_log_file_for assistant-autosave)
27
+ POLL_INTERVAL=5
28
+ SHUTTING_DOWN=0
29
+ BRIDGE_OUTAGE_NOTIFIED=0
30
+
31
+ send_notice_if_possible() {
32
+ local mode="$1"
33
+ shift || true
34
+ if [ "$CHANNEL_IMPL" != "feishu" ]; then
35
+ return 0
36
+ fi
37
+ if [ -z "${FEISHU_APP_ID:-}" ] || [ -z "${FEISHU_APP_SECRET:-}" ]; then
38
+ return 0
39
+ fi
40
+ node "$WORK_ALLY_IMPLEMENTATION_DIR/bridge/src/system-notify.ts" "$mode" "$@" >/dev/null 2>&1 || true
41
+ }
42
+
43
+ write_supervisor_health() {
44
+ local status="${1:-running}"
45
+ work_ally_write_health supervisor "$status" "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": $$, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\", \"runtime\": \"$RUNTIME_MODE\", \"channel\": \"$CHANNEL_IMPL\"}"
46
+ }
47
+
48
+ stop_bridge_processes_keep_locks() {
49
+ {
50
+ work_ally_read_pid "$BRIDGE_PID_FILE" || true
51
+ work_ally_list_bridge_pids || true
52
+ } | awk 'NF && !seen[$0]++ { print $0 }' | while IFS= read -r pid; do
53
+ [ -n "$pid" ] || continue
54
+ if work_ally_is_pid_alive "$pid"; then
55
+ work_ally_terminate_pid "$pid"
56
+ fi
57
+ done
58
+
59
+ rm -f "$BRIDGE_PID_FILE"
60
+ work_ally_write_health bridge stopped '{}'
61
+ }
62
+
63
+ stop_autosave_process() {
64
+ local pid
65
+ pid=$(work_ally_read_pid "$AUTOSAVE_PID_FILE" || true)
66
+ if [ -n "$pid" ] && work_ally_is_pid_alive "$pid"; then
67
+ work_ally_terminate_pid "$pid"
68
+ fi
69
+ rm -f "$AUTOSAVE_PID_FILE"
70
+ work_ally_write_health assistant-autosave stopped '{}'
71
+ }
72
+
73
+ cleanup() {
74
+ SHUTTING_DOWN=1
75
+ stop_autosave_process
76
+ stop_bridge_processes_keep_locks
77
+ work_ally_release_channel_lock "$$"
78
+ work_ally_release_assistant_lock "$WORK_ALLY_ASSISTANT_NAME" "$$"
79
+ rm -f "$SUPERVISOR_PID_FILE"
80
+ write_supervisor_health stopped
81
+ }
82
+
83
+ trap cleanup EXIT INT TERM
84
+
85
+ start_autosave_background() {
86
+ local existing_pid autosave_pid
87
+
88
+ [ -n "${WORK_ALLY_ASSISTANT_HOME:-}" ] || return 0
89
+ work_ally_assistant_git_setup "$WORK_ALLY_ASSISTANT_HOME" "$ASSISTANT_GIT_REMOTE" || true
90
+
91
+ existing_pid=$(work_ally_read_pid "$AUTOSAVE_PID_FILE" || true)
92
+ if [ -n "$existing_pid" ] && work_ally_is_pid_alive "$existing_pid"; then
93
+ work_ally_write_health assistant-autosave running "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": ${existing_pid}, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\"}"
94
+ return 0
95
+ fi
96
+
97
+ nohup env \
98
+ WORK_ALLY_ASSISTANT_MODE="assistant" \
99
+ WORK_ALLY_ASSISTANT_NAME="$WORK_ALLY_ASSISTANT_NAME" \
100
+ WORK_ALLY_ASSISTANT_HOME="$WORK_ALLY_ASSISTANT_HOME" \
101
+ WORK_ALLY_ASSISTANT_CODEX_HOME="$WORK_ALLY_ASSISTANT_CODEX_HOME" \
102
+ WORK_ALLY_ASSISTANT_GIT_REMOTE="$ASSISTANT_GIT_REMOTE" \
103
+ WORK_ALLY_WORKSPACE_ROOT="$WORK_ALLY_WORKSPACE_ROOT" \
104
+ WORK_ALLY_STATE_DIR="$WORK_ALLY_STATE_DIR" \
105
+ WORK_ALLY_INSTALL_ROOT="$WORK_ALLY_INSTALL_ROOT" \
106
+ WORK_ALLY_IMPLEMENTATION_DIR="$WORK_ALLY_IMPLEMENTATION_DIR" \
107
+ "$WORK_ALLY_IMPLEMENTATION_DIR/internal/modules/runtime/assistant-autosave.sh" >"$AUTOSAVE_LOG" 2>&1 &
108
+ echo $! > "$AUTOSAVE_PID_FILE"
109
+ sleep 1
110
+ autosave_pid=$(work_ally_read_pid "$AUTOSAVE_PID_FILE" || true)
111
+ if [ -n "$autosave_pid" ] && work_ally_is_pid_alive "$autosave_pid"; then
112
+ work_ally_write_health assistant-autosave running "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": ${autosave_pid}, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\"}"
113
+ return 0
114
+ fi
115
+
116
+ rm -f "$AUTOSAVE_PID_FILE"
117
+ work_ally_write_health assistant-autosave stopped '{}'
118
+ return 1
119
+ }
120
+
121
+ start_bridge_background() {
122
+ local bridge_count bridge_pid
123
+
124
+ bridge_count=$(work_ally_bridge_pid_count)
125
+ if [ "$bridge_count" -gt 1 ]; then
126
+ work_ally_warn "Multiple bridge processes detected for this workspace; restarting bridge"
127
+ stop_bridge_processes_keep_locks
128
+ fi
129
+
130
+ bridge_pid=$(work_ally_primary_bridge_pid || true)
131
+ if [ -n "$bridge_pid" ] && work_ally_is_pid_alive "$bridge_pid"; then
132
+ echo "$bridge_pid" > "$BRIDGE_PID_FILE"
133
+ work_ally_write_health bridge running "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": ${bridge_pid}, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\", \"channel\": \"$CHANNEL_IMPL\", \"runtime\": \"$RUNTIME_MODE\"}"
134
+ return 0
135
+ fi
136
+
137
+ work_ally_note "Starting work-ally bridge"
138
+ nohup env \
139
+ CODEX_HOME="$WORK_ALLY_ASSISTANT_CODEX_HOME" \
140
+ WORK_ALLY_ASSISTANT_MODE="assistant" \
141
+ WORK_ALLY_ASSISTANT_NAME="$WORK_ALLY_ASSISTANT_NAME" \
142
+ WORK_ALLY_ASSISTANT_HOME="$WORK_ALLY_ASSISTANT_HOME" \
143
+ WORK_ALLY_ASSISTANT_CODEX_HOME="$WORK_ALLY_ASSISTANT_CODEX_HOME" \
144
+ WORK_ALLY_WORKSPACE_ROOT="$WORK_ALLY_WORKSPACE_ROOT" \
145
+ WORK_ALLY_STATE_DIR="$WORK_ALLY_STATE_DIR" \
146
+ WORK_ALLY_INSTALL_ROOT="$WORK_ALLY_INSTALL_ROOT" \
147
+ WORK_ALLY_IMPLEMENTATION_DIR="$WORK_ALLY_IMPLEMENTATION_DIR" \
148
+ node "$WORK_ALLY_IMPLEMENTATION_DIR/bridge/src/server.ts" >"$BRIDGE_LOG" 2>&1 &
149
+ echo $! > "$BRIDGE_PID_FILE"
150
+ sleep 2
151
+ bridge_pid=$(work_ally_read_pid "$BRIDGE_PID_FILE" || true)
152
+ if [ -n "$bridge_pid" ] && work_ally_is_pid_alive "$bridge_pid"; then
153
+ work_ally_write_health bridge running "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": ${bridge_pid}, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\", \"channel\": \"$CHANNEL_IMPL\", \"runtime\": \"$RUNTIME_MODE\"}"
154
+ return 0
155
+ fi
156
+
157
+ rm -f "$BRIDGE_PID_FILE"
158
+ work_ally_write_health bridge stopped '{}'
159
+ return 1
160
+ }
161
+
162
+ recover_bridge_if_needed() {
163
+ local bridge_count bridge_pid
164
+
165
+ bridge_count=$(work_ally_bridge_pid_count)
166
+ if [ "$bridge_count" -eq 1 ]; then
167
+ bridge_pid=$(work_ally_primary_bridge_pid || true)
168
+ if [ -n "$bridge_pid" ] && work_ally_is_pid_alive "$bridge_pid"; then
169
+ echo "$bridge_pid" > "$BRIDGE_PID_FILE"
170
+ work_ally_write_health bridge running "{\"assistant\": \"$WORK_ALLY_ASSISTANT_NAME\", \"pid\": ${bridge_pid}, \"workspaceRoot\": \"$WORK_ALLY_WORKSPACE_ROOT\", \"channel\": \"$CHANNEL_IMPL\", \"runtime\": \"$RUNTIME_MODE\"}"
171
+ BRIDGE_OUTAGE_NOTIFIED=0
172
+ return 0
173
+ fi
174
+ fi
175
+
176
+ if [ "$BRIDGE_OUTAGE_NOTIFIED" -eq 0 ]; then
177
+ send_notice_if_possible text "消息桥接已断开,正在恢复……"
178
+ BRIDGE_OUTAGE_NOTIFIED=1
179
+ fi
180
+
181
+ stop_bridge_processes_keep_locks
182
+ if start_bridge_background; then
183
+ send_notice_if_possible text "消息桥接已恢复。"
184
+ BRIDGE_OUTAGE_NOTIFIED=0
185
+ return 0
186
+ fi
187
+
188
+ work_ally_warn "Failed to recover bridge; will retry"
189
+ return 1
190
+ }
191
+
192
+ work_ally_assert_assistant_available "$WORK_ALLY_ASSISTANT_NAME"
193
+ work_ally_assert_channel_available
194
+ work_ally_claim_assistant_lock "$WORK_ALLY_ASSISTANT_NAME" "$$"
195
+ work_ally_claim_channel_lock "$$"
196
+ echo "$$" > "$SUPERVISOR_PID_FILE"
197
+ write_supervisor_health running
198
+
199
+ work_ally_assistant_git_setup "$WORK_ALLY_ASSISTANT_HOME" "$ASSISTANT_GIT_REMOTE" || true
200
+ work_ally_assistant_git_checkpoint "$WORK_ALLY_ASSISTANT_HOME" "start assistant $WORK_ALLY_ASSISTANT_NAME" || true
201
+
202
+ if ! start_bridge_background; then
203
+ write_supervisor_health failed
204
+ exit 1
205
+ fi
206
+ start_autosave_background || true
207
+
208
+ while :; do
209
+ [ "$SHUTTING_DOWN" = "0" ] || exit 0
210
+ work_ally_write_assistant_lock "$WORK_ALLY_ASSISTANT_NAME" "$$"
211
+ work_ally_write_channel_lock "$$"
212
+ write_supervisor_health running
213
+ recover_bridge_if_needed || true
214
+ start_autosave_background || true
215
+ sleep "$POLL_INTERVAL"
216
+ done
@@ -0,0 +1,26 @@
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
+ if [ -z "${WORK_ALLY_ASSISTANT_NAME:-}" ]; then
9
+ WORK_ALLY_ASSISTANT_NAME=$(work_ally_resolve_assistant_name_from_args "$@" || true)
10
+ fi
11
+ if [ -n "${WORK_ALLY_ASSISTANT_NAME:-}" ]; then
12
+ work_ally_sync_workspace_root_from_assistant "$WORK_ALLY_ASSISTANT_NAME"
13
+ work_ally_hydrate_assistant_context "$WORK_ALLY_ASSISTANT_NAME"
14
+ fi
15
+ work_ally_ensure_state_dirs
16
+
17
+ work_ally_note "Install-first mode: update is handled by npm."
18
+ cat <<EOF2
19
+ Run one of these in your normal terminal:
20
+ npm i -g work-ally@alpha
21
+ npm update -g work-ally
22
+ Then verify with:
23
+ ally status --assistant <name>
24
+ EOF2
25
+
26
+ work_ally_ok "Update guidance shown"
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "work-ally",
3
+ "version": "0.2.0-alpha.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "ally": "ally.sh"
8
+ },
9
+ "files": [
10
+ "ally.sh",
11
+ "bridge/",
12
+ "internal/",
13
+ "runtime/",
14
+ "templates/",
15
+ "docs/",
16
+ "skills/",
17
+ "package.json",
18
+ "README.md",
19
+ "PRODUCT.md",
20
+ "DASHBOARD.md",
21
+ "AGENTS.md"
22
+ ],
23
+ "scripts": {
24
+ "test": "npm run test:shell && npm run test:bridge",
25
+ "test:full": "npm test",
26
+ "test:fast": "node --test \"tests/unit/**/*.test.mjs\" \"tests/integration/**/*.test.mjs\"",
27
+ "test:maintainer": "bash dev/run-regression-suite.sh",
28
+ "test:shell": "bash tests/shell/smoke.sh && bash tests/shell/assistant.sh && bash tests/shell/assistant-remove.sh && bash tests/shell/assistant-rename.sh && bash tests/shell/assistant-lock.sh && bash tests/shell/global-entrypoint.sh && bash tests/shell/global-manage.sh && bash tests/shell/security.sh && bash tests/shell/ops.sh && bash tests/shell/lifecycle.sh && bash tests/shell/channel-lock.sh && bash tests/shell/mcp.sh && bash tests/shell/routines.sh && bash tests/shell/recovery.sh && bash tests/shell/service-mode.sh && bash tests/shell/bridge-health-fallback.sh && bash tests/shell/startup-delivery-gate.sh && bash tests/shell/status-health.sh && bash tests/shell/codex-runtime.sh && bash tests/shell/codex-handoff.sh && bash tests/shell/supervised-start-boundary.sh",
29
+ "test:bridge": "node --test \"tests/unit/**/*.test.mjs\" \"tests/integration/**/*.test.mjs\"",
30
+ "dev:debug-workspace": "bash dev/prepare-debug-workspace.sh",
31
+ "bridge:start": "node bridge/src/server.ts",
32
+ "runtime:health": "node runtime/host/healthcheck-codex-app-server.ts",
33
+ "runtime:probe": "node runtime/host/probe-codex-app-server.ts",
34
+ "release:alpha": "bash script/release-npm-alpha.sh"
35
+ },
36
+ "dependencies": {
37
+ "@larksuiteoapi/node-sdk": "^1.50.0",
38
+ "cron-parser": "^5.2.0",
39
+ "yaml": "^2.8.1"
40
+ }
41
+ }
File without changes
File without changes
@@ -0,0 +1,22 @@
1
+ import { CodexRuntimeClient } from '../../bridge/src/runtime-client.ts';
2
+
3
+ async function main() {
4
+ const client = new CodexRuntimeClient({
5
+ codexHome: process.env.CODEX_HOME || null,
6
+ });
7
+
8
+ try {
9
+ const ok = await client.healthcheck();
10
+ console.log(JSON.stringify({ transport: 'stdio', ok }));
11
+ if (!ok) {
12
+ process.exitCode = 1;
13
+ }
14
+ } finally {
15
+ await client.disconnect().catch(() => undefined);
16
+ }
17
+ }
18
+
19
+ main().catch((error) => {
20
+ console.error(error);
21
+ process.exit(1);
22
+ });
@@ -0,0 +1,66 @@
1
+ import { CodexRuntimeClient } from '../../bridge/src/runtime-client.ts';
2
+
3
+ function parseRounds(argv: string[]): number {
4
+ const index = argv.findIndex((arg) => arg === '--rounds');
5
+ if (index >= 0) {
6
+ const value = Number(argv[index + 1] || '');
7
+ if (!Number.isInteger(value) || value <= 0) {
8
+ throw new Error('--rounds must be a positive integer');
9
+ }
10
+ return value;
11
+ }
12
+
13
+ const inline = argv.find((arg) => arg.startsWith('--rounds='));
14
+ if (inline) {
15
+ const value = Number(inline.slice('--rounds='.length));
16
+ if (!Number.isInteger(value) || value <= 0) {
17
+ throw new Error('--rounds must be a positive integer');
18
+ }
19
+ return value;
20
+ }
21
+
22
+ return 20;
23
+ }
24
+
25
+ async function main() {
26
+ const rounds = parseRounds(process.argv.slice(2));
27
+ const client = new CodexRuntimeClient({
28
+ codexHome: process.env.CODEX_HOME || null,
29
+ });
30
+
31
+ try {
32
+ const ok = await client.healthcheck();
33
+ if (!ok) {
34
+ throw new Error('Codex runtime is not reachable');
35
+ }
36
+
37
+ const thread = await client.startThread(process.cwd());
38
+ const replies: string[] = [];
39
+ for (let index = 1; index <= rounds; index += 1) {
40
+ const expected = String(index);
41
+ const result = await client.runTurn({
42
+ threadId: thread.id,
43
+ prompt: 'Reply with exactly: ' + expected,
44
+ messageId: 'ping-pong-' + expected,
45
+ cwd: process.cwd(),
46
+ });
47
+ const actual = String(result.reply || '').trim();
48
+ if (result.status !== 'completed') {
49
+ throw new Error('ping-pong round failed at ' + expected + ': ' + result.status + ' ' + String(result.error || ''));
50
+ }
51
+ if (actual !== expected) {
52
+ throw new Error('ping-pong mismatch at round ' + expected + ': expected "' + expected + '", got "' + actual + '"');
53
+ }
54
+ replies.push(actual);
55
+ }
56
+
57
+ console.log(JSON.stringify({ transport: 'stdio', threadId: thread.id, rounds, lastReply: replies[replies.length - 1] }, null, 2));
58
+ } finally {
59
+ await client.disconnect().catch(() => undefined);
60
+ }
61
+ }
62
+
63
+ main().catch((error) => {
64
+ console.error(error);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,115 @@
1
+ import { parseMcpToolApprovalRules } from '../../bridge/src/config.ts';
2
+ import { CodexRuntimeClient } from '../../bridge/src/runtime-client.ts';
3
+ import { maybeAutoResolveRuntimeUserInput } from '../../bridge/src/runtime-user-input.ts';
4
+ import type { UserInputRequest } from '../../bridge/src/types.ts';
5
+
6
+ interface ProbeUserInputEvent {
7
+ kind: UserInputRequest['kind'];
8
+ requestId: string | number;
9
+ prompt: string;
10
+ }
11
+
12
+ function sandboxPolicyFromEnv(): unknown | undefined {
13
+ const mode = (process.env.WORK_ALLY_CODEX_SANDBOX_MODE || '').trim();
14
+ if (!mode) {
15
+ return undefined;
16
+ }
17
+ if (mode === 'danger-full-access') {
18
+ return { type: 'dangerFullAccess' as const };
19
+ }
20
+ if (mode === 'read-only') {
21
+ return {
22
+ type: 'readOnly' as const,
23
+ networkAccess: true,
24
+ access: {
25
+ type: 'restricted' as const,
26
+ includePlatformDefaults: true,
27
+ readableRoots: [process.cwd()],
28
+ },
29
+ };
30
+ }
31
+ return {
32
+ type: 'workspaceWrite' as const,
33
+ networkAccess: true,
34
+ writableRoots: [process.cwd()],
35
+ };
36
+ }
37
+
38
+ function summarizeUserInput(request: UserInputRequest): ProbeUserInputEvent {
39
+ const prompt = request.elicitation?.message || request.questions?.map((question) => question.question).join(' | ') || '';
40
+ return {
41
+ kind: request.kind,
42
+ requestId: request.requestId,
43
+ prompt,
44
+ };
45
+ }
46
+
47
+ async function main() {
48
+ const prompt = process.argv.slice(2).join(' ') || 'Reply with exactly: OK';
49
+ const approvalPolicy = (process.env.WORK_ALLY_CODEX_APPROVAL_POLICY || '').trim() || undefined;
50
+ const sandboxPolicy = sandboxPolicyFromEnv();
51
+ const rules = parseMcpToolApprovalRules(process.env.WORK_ALLY_MCP_TOOL_APPROVAL_ALLOWLIST);
52
+ const client = new CodexRuntimeClient({
53
+ codexHome: process.env.CODEX_HOME || null,
54
+ });
55
+ const unresolvedUserInputs: ProbeUserInputEvent[] = [];
56
+ const autoResolvedUserInputs: Array<ProbeUserInputEvent & { matchedRule: string }> = [];
57
+
58
+ try {
59
+ const ok = await client.healthcheck();
60
+ if (!ok) {
61
+ throw new Error('Codex runtime is not reachable');
62
+ }
63
+
64
+ const thread = await client.startThread(process.cwd());
65
+ const result = await client.runTurn({
66
+ threadId: thread.id,
67
+ prompt,
68
+ messageId: 'probe-codex-runtime',
69
+ cwd: process.cwd(),
70
+ approvalPolicy,
71
+ sandboxPolicy,
72
+ onProgress: (text) => console.log('[progress]', text),
73
+ onApproval: (approval) => console.log('[approval]', JSON.stringify(approval)),
74
+ onUserInput: async (request) => {
75
+ const autoResolution = maybeAutoResolveRuntimeUserInput(request, rules);
76
+ if (autoResolution?.answers) {
77
+ await client.resolveUserInput(request.requestId, { answers: autoResolution.answers });
78
+ const event = summarizeUserInput(request);
79
+ autoResolvedUserInputs.push({ ...event, matchedRule: autoResolution.matchedRule });
80
+ console.log('[user-input:auto]', JSON.stringify({ ...event, matchedRule: autoResolution.matchedRule }));
81
+ return;
82
+ }
83
+ if (autoResolution?.elicitationResponse) {
84
+ await client.resolveElicitation(request.requestId, autoResolution.elicitationResponse);
85
+ const event = summarizeUserInput(request);
86
+ autoResolvedUserInputs.push({ ...event, matchedRule: autoResolution.matchedRule });
87
+ console.log('[elicitation:auto]', JSON.stringify({ ...event, matchedRule: autoResolution.matchedRule }));
88
+ return;
89
+ }
90
+
91
+ const event = summarizeUserInput(request);
92
+ unresolvedUserInputs.push(event);
93
+ console.log('[user-input:manual]', JSON.stringify(event));
94
+ if (request.kind === 'mcp_elicitation') {
95
+ await client.resolveElicitation(request.requestId, { action: 'cancel', content: null });
96
+ } else {
97
+ await client.resolveUserInput(request.requestId, { answers: {} });
98
+ }
99
+ },
100
+ });
101
+
102
+ if (unresolvedUserInputs.length > 0) {
103
+ throw new Error(`Probe encountered manual user input: ${JSON.stringify(unresolvedUserInputs)}`);
104
+ }
105
+
106
+ console.log(JSON.stringify({ transport: 'stdio', thread, result, autoResolvedUserInputs }, null, 2));
107
+ } finally {
108
+ await client.disconnect().catch(() => undefined);
109
+ }
110
+ }
111
+
112
+ main().catch((error) => {
113
+ console.error(error);
114
+ process.exit(1);
115
+ });
@@ -0,0 +1,9 @@
1
+ # archive-reader
2
+
3
+ Use this skill when you need to inspect workspace archive files under `memory/archive/` and extract the durable facts worth keeping.
4
+
5
+ ## Rules
6
+
7
+ - Treat archive files as raw black-box material.
8
+ - Prefer reading the smallest useful date range first.
9
+ - Distinguish raw events from durable conclusions.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: feishu-production-debug
3
+ description: Use this skill when a real Feishu conversation path appears broken or missing and you need to debug the production chain from event delivery to thread routing to reply delivery, without guessing or immediately blaming the bridge core.
4
+ ---
5
+
6
+ # Feishu Production Debug
7
+
8
+ Use this skill for real message-path incidents seen in Feishu.
9
+
10
+ Do not use this skill when:
11
+
12
+ - the issue is only a local formatter or unit-test failure with no live symptom
13
+ - the task is purely product design or permission brainstorming
14
+ - the problem is already known to be outside Feishu message delivery
15
+
16
+ ## Quick start
17
+
18
+ 1. Read `references/feishu-debug-order.md` first.
19
+ 2. Read `references/platform-permission-baseline.md` if the symptom involves missing messages, group triggers, or bot visibility.
20
+ 3. Establish the exact symptom, time window, and conversation type first: private chat, group mention, reply context, or outbound delivery.
21
+ 4. Debug in strict order from inbound receipt to outbound reply.
22
+
23
+ ## Output contract
24
+
25
+ Always answer with:
26
+
27
+ 1. most likely broken stage
28
+ 2. evidence
29
+ 3. next verification step or fix direction
30
+
31
+ ## Working rules
32
+
33
+ - First ask whether the message ever reached the bridge.
34
+ - If there is no inbound event evidence, check Feishu platform permissions and delivery before touching bridge logic.
35
+ - Keep private chat and group-mention paths separate; they are different triggers and different threads.
36
+ - Treat routing and reply-delivery as different stages.
37
+ - Prefer fact-based localization over broad guesses like “bridge probably swallowed it.”
@@ -0,0 +1,49 @@
1
+ # Feishu Debug Order
2
+
3
+ ## Strict debug order
4
+
5
+ ### 1. Confirm the symptom
6
+
7
+ Capture:
8
+
9
+ - exact user message or action
10
+ - approximate time
11
+ - private chat or group mention
12
+ - whether the issue is missing trigger, wrong thread, or missing reply
13
+
14
+ ### 2. Check inbound receipt first
15
+
16
+ Ask:
17
+
18
+ - Did the bridge receive the event at all?
19
+ - Is there inbound evidence in logs or ledger?
20
+
21
+ If the answer is no, do not start with routing speculation.
22
+
23
+ ### 3. Check platform permissions and delivery
24
+
25
+ Especially verify:
26
+
27
+ - private chat read permission
28
+ - group `@bot` event permission
29
+ - whether “receive all group messages” was intentionally left disabled
30
+
31
+ ### 4. Check routing
32
+
33
+ If inbound exists, ask:
34
+
35
+ - Was the message mapped to the expected assistant?
36
+ - Was it mapped to the expected private or group thread?
37
+ - Was reply context or sender context resolved correctly?
38
+
39
+ ### 5. Check outbound reply delivery
40
+
41
+ If the assistant worked but the user saw nothing, ask:
42
+
43
+ - Did a reply artifact exist?
44
+ - Did outbound delivery fail?
45
+ - Did the bridge suppress or dedupe a stale result?
46
+
47
+ ## Default rule
48
+
49
+ Never jump straight from “user saw no reply” to “Codex failed.”
@@ -0,0 +1,23 @@
1
+ # Feishu Platform Permission Baseline
2
+
3
+ ## Minimal supported baseline
4
+
5
+ For the current shipped model, the expected Feishu bot permissions are:
6
+
7
+ - enable reading private messages sent to the bot
8
+ - enable receiving group events where the bot is explicitly mentioned
9
+ - do not enable “receive all group messages” unless you intentionally want unsupported mixed group traffic
10
+
11
+ ## Why this matters
12
+
13
+ ### Private chat
14
+
15
+ If private-message read permission is missing, the bridge will never receive the user's direct message.
16
+
17
+ ### Group chat
18
+
19
+ Current supported group model is explicit `@assistant` trigger.
20
+
21
+ If the platform is not configured to deliver group `@bot` events, the bridge cannot route anything, and there is nothing local to “recover.”
22
+
23
+ If “receive all group messages” is enabled, unsupported traffic may reach the bridge and create mixed-thread behavior. That outcome belongs to unsupported configuration, not the default product contract.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: issue-to-spec-triage
3
+ description: Use this skill when a user brings a new idea, complaint, bug report, request, or product question and you need to decide whether it is a small bugfix, a product boundary correction, a spec-worthy feature, or something that should stay in the idea pool.
4
+ ---
5
+
6
+ # Issue To Spec Triage
7
+
8
+ Use this skill when the immediate job is to classify an incoming issue or idea, not to directly design or implement the solution.
9
+
10
+ Do not use this skill when:
11
+
12
+ - the user has already explicitly asked to draft, refine, review, or score a spec
13
+ - the task is already clearly an implementation task
14
+ - the issue is obviously a tiny wording fix or a low-risk local bugfix with no product-semantic impact
15
+
16
+ ## Quick start
17
+
18
+ 1. Read `references/triage-rules.md` first.
19
+ 2. Load only the minimum project context you need, usually from `PRODUCT.md`, `DASHBOARD.md`, `AGENTS.md`, or the most relevant existing spec.
20
+ 3. Decide the classification before proposing a solution.
21
+ 4. If the outcome is `spec-needed`, hand off to `skills/product-spec/` rather than mixing triage and spec writing into one blurry step.
22
+
23
+ ## Output contract
24
+
25
+ Always answer in this order:
26
+
27
+ 1. classification
28
+ 2. short reason
29
+ 3. next action
30
+
31
+ Valid classifications:
32
+
33
+ - `small-bugfix`
34
+ - `boundary-fix`
35
+ - `spec-needed`
36
+ - `hold`
37
+
38
+ ## Working rules
39
+
40
+ - Prioritize identifying the real product problem over accepting the user's first proposed solution.
41
+ - Ask only the minimum clarifying questions needed to classify correctly.
42
+ - If a change would alter product meaning, default behavior, user path, long-term maintenance semantics, or cross-module/system boundaries, treat it as `spec-needed`.
43
+ - If the issue is already covered by an existing spec or shipped contract, prefer `boundary-fix` or `small-bugfix` over opening a duplicate spec.
44
+ - If the idea is real but not yet actionable, classify it as `hold` and point to the appropriate planning surface.