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,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.
|